iOS

[iOS] TVING(티빙) 로그인 화면 클론 코딩 UIKit 1편 - View 작업

띵지니어 2024. 4. 8. 20:38

안녕하세요 띵지니어 😼 입니다.

오늘은 TVING 앱의 로그인 화면(View)만 똑같이 구현을 해보려고 합니다.
전체 코드가 궁금하신 분은 맨 아래 참고해 주세요!

다음은 우리가 구현해야 할 View입니다.

 

저는 UIKit 코드베이스로 구현해보려고 하기 때문에,
먼저 코드베이스로 프로젝트 세팅을 모르시는 분들은 아래 링크 참고해 주세요!


https://thingjin.tistory.com/entry/%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B3%B4%EB%93%9C-%EC%97%86%EC%9D%B4-UIKit%EC%9C%BC%EB%A1%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85-%EC%BD%94%EB%93%9C%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%A0%95

 

[iOS] 스토리보드 없이 UIKit으로 프로젝트 세팅: 코드베이스 설정

안녕하세요 띵지니어 😼 입니다. 이번 포스팅은 UIKit 프레임 워크에 스토리보드를 사용하지 않고 코드 베이스 로 프로젝트를 진행해야 할 때 초기 세팅을 어떻게 해야 하는지에 대해 알아보도

thingjin.tistory.com

 

UIKit 코드 베이스로 기초 세팅을 했으면 아래 프로젝트 구조로 시작하겠습니다.


폰트 Assets 추가

 


프로젝트를 시작하기 전에 폰트와, 색상은 따로 넣어주시면 됩니다.
색상은 따로 등록 안 하셔도 되고, 폰트 추가를 하고 싶다면 아래 링크 참고 해주세요!

https://thingjin.tistory.com/entry/iOS-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%ED%8F%B0%ED%8A%B8Font-%EB%84%A3%EB%8A%94-%EB%B2%95-UIKit-codebase

 

[iOS] 프로젝트에 폰트(Font) 넣는 법

오늘은 프로젝트 하면서 Apple에서 제공되는 font가 아닌 외부 폰트를 가져와서 프로젝트에 적용 시켜보는 작업을 해보도록 할게요 🍀 먼저 외부에서 font를 다운받아서 로컬로 다운 받아요 저는

thingjin.tistory.com

 


라이브러리 추가 ‼️

 

⭐️ 추가로 LayoutSnapKit, UIComponentThen 라이브러리를 사용할 예정입니다~

SPM으로 라이브러리를 다운로드해 줄게요

 

라이브러리 사용법을 모르시는 분은 아래 링크를 참고해 주세요!

https://thingjin.tistory.com/entry/iOS-SPMSwift-Package-Manager-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95

 

[iOS] SPM(Swift Package Manager) 설치 방법

안녕하세요 띵지니어 😼 입니다. 오늘은 SPM으로 외부 라이브러리를 설치하는 방법에 대해서 알아볼게요 프로젝트가 초기 세팅도 참고해 보고 싶다면 아랫글 먼저 보고 오시면 좋아요! 🍏 코드

thingjin.tistory.com

 

기본 세팅한 프로젝트의 파일 구조입니다.

 

SceneDelegate.swift 파일은 아래 처럼 해주세요.

//
//  SceneDelegate.swift
//  tvingProject
//
//  Created by 이명진 on 4/8/24.
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        self.window = UIWindow(windowScene: windowScene)

        let navigationController = UINavigationController(rootViewController: LoginViewController())
        self.window?.rootViewController = navigationController        
        self.window?.makeKeyAndVisible()
    }
}

 

이제 본격적으로 티빙 로그인 화면을 클론 코딩을 시작해 봅시다.

 


 

🧑🏻‍💻 티빙 로그인 화면 클론 코딩하기

 

먼저 View의 구조부터 짤게요 

빨간색은 StackView 입니다.

스토리보드가 아닌 코드 베이스로 구현하기 때문에
설명할 때 먼저 그리고 하는 게 편하더라고요.

저는 위 파일 구조처럼 LoginViewController.swift 파일에 구현해 줄게요.

레이아웃과 뷰 등록은 맨 마지막에 설정할게요!
(따라서 화면에 아무 컴포넌트도 추가가 안 됐을 거예요)

폰트와 텍스트 색은 따라 하지 말고 자유롭게 해 주세요!
(커스텀으로 등록해야 하기 때문)


LoginViewController.swift 파일에 작성을 해봅시다.

 

1.  Title 구현

먼저 "TVING ID 로그인" Label을 만들어 줍니다.


 

2.  아이디 비밀번호 입력하는 부분 구현

StackView 안에 2개의 TextField를 넣어 줄 거예요.

참고로 이렇게 짜면 Placeholder가 텍스트 필드에 붙어버립니다

 

허허 20 정도만 띄어 볼까요~?

따로 "UITextField+" 라는 파일을 만들어서 addLeftPadding 함수를 만들어 줍니다.

이런 경우 패딩을 추가한다~!라고 이해하시면 됩니다.

//
//  UITextField+.swift
//  tvingProject
//
//  Created by 이명진 on 4/8/24.
//

import UIKit

extension UITextField {
	/// width 만큼의 뷰를 textField에 추가해 줍니다.
    func addLeftPadding(width: CGFloat) {
        let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.height))
        self.leftView = paddingView
        self.leftViewMode = .always
    }
}

 

이제 아래처럼 속성을 추가할 수 있어요!


 

3.  로그인 버튼 구현

비슷한가요?


 

4.  아이디 찾기 | 비밀번호 찾기 Label 로 구현


 

5.  아직 계정이 없으신가요? TVING ID 회원가입 하기 구현

모든 속성을 뷰에 추가해 주고,
레이아웃을 잡으면 아래와 같은 뷰를 완성시킬 수 있습니다.
레이아웃 코드는 맨 아래에 있습니다!

 

좀 비슷한가요~?
폰트와 색상은 이해해주세요!

아래는 LoginViewController.swift 의 전체코드입니다.

//
//  LoginViewController.swift
//  tvingProject
//
//  Created by 이명진 on 4/8/24.
//

import UIKit

import SnapKit
import Then

final class LoginViewController: UIViewController {
    
    // MARK: - UIComponents
    
    private lazy var loginTitle = UILabel().then {
        $0.text = "TVING ID 로그인"
        $0.font = UIFont.pretendardFont(weight: 500, size: 23)
        $0.textColor = .gray1
    }
    
    private lazy var idTextField = UITextField().then {
        $0.attributedPlaceholder = NSAttributedString(
            string: "아이디",
            attributes: [
                .font: UIFont.pretendardFont(weight: 600, size: 15),
                .foregroundColor: UIColor.gray1
            ]
        )
        
        $0.addLeftPadding(width: 22)
        $0.layer.cornerRadius = 3
        $0.backgroundColor = .gray4
    }
    
    private let passwordTextField = UITextField().then {
        $0.attributedPlaceholder = NSAttributedString(
            string: "비밀번호",
            attributes: [
                .font: UIFont.pretendardFont(weight: 600, size: 15),
                .foregroundColor: UIColor.gray1
            ]
        )
        
        $0.addLeftPadding(width: 22)
        $0.isSecureTextEntry = true
        $0.layer.cornerRadius = 3
        $0.backgroundColor = .gray4
    }
    
    private lazy var vStackViewLogin = UIStackView(
        arrangedSubviews: [
            idTextField,
            passwordTextField
        ]
    ).then {
        $0.axis = .vertical
        $0.spacing = 7
        $0.distribution = .fillEqually
    }
    
    private lazy var loginButton = UIButton().then {
        $0.setTitle("로그인 하기", for: .normal)
        $0.backgroundColor = .clear
        $0.layer.borderWidth = 1
        $0.layer.borderColor = UIColor.gray4.cgColor
        $0.layer.cornerRadius = 3
    }
    
    private let idSearch = UILabel().then {
        $0.text = "아이디 찾기"
        $0.font = .pretendardFont(weight: 600, size: 14)
        $0.textColor = .gray2
    }
    
    private let dividerLabel = UILabel().then {
        $0.text = "|"
        $0.textColor = .gray4
    }
    
    private let passwordSearch = UILabel().then {
        $0.text = "비밀번호 찾기"
        $0.font = .pretendardFont(weight: 600, size: 14)
        $0.textColor = .gray2
    }
    
    private lazy var hStackViewInfoFirst = UIStackView(
        arrangedSubviews: [
            idSearch,
            dividerLabel,
            passwordSearch
        ]
    ).then {
        $0.spacing = 36
        $0.axis = .horizontal
        $0.distribution = .equalSpacing
    }
    
    private let notAccountLabel = UILabel().then {
        $0.text = "아직 계정이 없으신가요?"
        $0.font = .pretendardFont(weight: 600, size: 14)
        $0.textColor = .gray3
    }
    
    private let makeNickNameLabel = UILabel().then {
        
        let attributes: [NSAttributedString.Key: Any] = [
            .underlineStyle: NSUnderlineStyle.single.rawValue,
            .foregroundColor: UIColor.gray2,
            .font: UIFont.pretendardFont(weight: 400, size: 14)
        ]
        
        $0.attributedText = NSAttributedString(
            string: "TVING ID 회원가입하기",
            attributes: attributes
        )
    }
    
    private lazy var hStackViewInfoSecond = UIStackView(
        arrangedSubviews: [
            notAccountLabel,
            makeNickNameLabel
        ]
    ).then {
        $0.spacing = 8
        $0.axis = .horizontal
        $0.distribution = .equalSpacing
    }
    
    // MARK: - Life Cycles
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setUI()
        setHierarchy()
        setLayout()
    }
    
    // MARK: - UI & Layout
    
    private func setUI() {
        view.backgroundColor = .black
    }
    
    private func setHierarchy() {
        [
            loginTitle,
            vStackViewLogin,
            loginButton,
            hStackViewInfoFirst,
            hStackViewInfoSecond
        ].forEach {
            self.view.addSubview($0)
        }
    }
    
    private func setLayout() {
        loginTitle.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(self.view.safeAreaLayoutGuide).offset(4)
        }
        
        vStackViewLogin.snp.makeConstraints {
            $0.top.equalTo(self.loginTitle.snp.bottom).offset(31)
            $0.leading.trailing.equalToSuperview().inset(20)
            $0.height.equalTo(52 + 52 + 7)
        }
        
        loginButton.snp.makeConstraints {
            $0.top.equalTo(vStackViewLogin.snp.bottom).offset(21)
            $0.height.equalTo(52)
            $0.leading.trailing.equalToSuperview().inset(20)
        }
        
        hStackViewInfoFirst.snp.makeConstraints {
            $0.top.equalTo(loginButton.snp.bottom).offset(31)
            $0.height.equalTo(22)
            $0.leading.trailing.equalToSuperview().inset(85)
        }
        
        hStackViewInfoSecond.snp.makeConstraints {
            $0.top.equalTo(hStackViewInfoFirst.snp.bottom).offset(31)
            $0.height.equalTo(22)
            $0.leading.trailing.equalToSuperview().inset(51)
        }
    }    
}

 

 

이슈가 발생하면 폰트와 색상 문제일 수 있습니다.

잘못된 부분과 질문하고 싶은 부분은
언제든지 댓글로 남겨주시면 감사하겠습니다!

2편에서는 뷰 이동과 사용자 입력이 나타났을 때 액션을 추가해 보겠습니다

Xcode 15.1
iOS 17.4.1
MacOS Sonoma 14.2.1
환경에서 작성 한 글입니다.