Clone App/Twitter

[Twitter Clone] Add logo, Design ProfileView

밤새는 탐험가89 2024. 5. 26. 02:00

🟨 구현화면

 

🟨 구현 순서

  • navigationItem 의 titleView에 logo 이미지 설정
  • profile 관련된 Controller 및 profileView 생성

 

🟨 HomeViewController.swift 코드 구현

  • configureNavigationBar() 메서드로 네비게이션 부분 설정 
  • 로고를 네비게이션에 할당: 1. 로고 이미지뷰 생성 및 설정 → 2. 로고 이미지뷰를 담을 뷰 생성 → 3. 해당 뷰를 네비게이션에 설정
import UIKit

class HomeViewController: UIViewController {
    
    // 네비게이션 부분에 logo 이미지 추가 
    private func configureNavigationBar() {
        let size: CGFloat = 36
        let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: size, height: size))
        logoImageView.contentMode = .scaleAspectFill
        logoImageView.image = UIImage(named: "logo")
        
        let middleView = UIView(frame: CGRect(x: 0, y: 0, width: size, height: size))
        middleView.addSubview(logoImageView)
        navigationItem.titleView = middleView
        
        
        // profile
        let profileImage = UIImage(systemName: "person")
        navigationItem.leftBarButtonItem = UIBarButtonItem(image: profileImage, style: .plain, target: self, action: #selector(didTapProfile))
    }
    
    @objc private func didTapProfile() {
        print("didTapProfile() - called")
        let profileVC = ProfileViewController()
        navigationController?.pushViewController(profileVC, animated: true)
    }
    
    private let timelineTableView: UITableView = {
        let tableView = UITableView()
        tableView.register(TweetTableViewCell.self, forCellReuseIdentifier: TweetTableViewCell.identifier)
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(timelineTableView)
        
        timelineTableView.delegate = self
        timelineTableView.dataSource = self
        
        
        configureNavigationBar()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        timelineTableView.frame = view.bounds
    }
}

extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TweetTableViewCell.identifier, 
                                                       for: indexPath) as? TweetTableViewCell  
        else {
            return UITableViewCell()
        }
        
        // 델리게이트 패턴에 대한 대리자를 HomeViewController 설정
        cell.delegate = self
        
        return cell
    }
    
}

extension HomeViewController: TweetTableViewCellDelegate {
    func tweetTableViewCellDidTapReply() {
        print("tweetTableViewCellDidTapReply() - called")
    }
    
    func tweetTableViewCellDidTapRetweet() {
        print("tweetTableViewCellDidTapRetweet() - called")
    }
    
    func tweetTableViewCellDidTapLike() {
        print("tweetTableViewCellDidTapLike() - called")
    }
    
    func tweetTableViewCellDidTapShare() {
        print("tweetTableViewCellDidTapShare() - called")
    }

}

 

 

🟨 ProfileViewController.swift 코드 구현

  • 기존에 사용했던 TweetTableViewCell을 사용하여 테이블 뷰의 레이아웃을 잡는다.
  • tableHeaderView를 통해 table의 Header 부분에 Profile 내용을 담는다. 
import UIKit

class ProfileViewController: UIViewController {
    
    
    private let profileTableView: UITableView = {
        
        let tableView = UITableView()
        tableView.register(TweetTableViewCell.self, forCellReuseIdentifier: TweetTableViewCell.identifier)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        navigationItem.title = "Profile"
        
        view.addSubview(profileTableView)
        
        profileTableView.delegate = self
        profileTableView.dataSource = self
        
        configureConstraints()
        
        
        let headerView = ProfileTableViewHeader(frame: CGRect(x: 0, y: 0, width: profileTableView.frame.width, height: 350))
        profileTableView.tableHeaderView = headerView
    }
    
    
    private func configureConstraints() {
        
        let profileTableViewConstraints = [
            profileTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            profileTableView.topAnchor.constraint(equalTo: view.topAnchor),
            profileTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            profileTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ]
        
        NSLayoutConstraint.activate(profileTableViewConstraints)
    }
}

extension ProfileViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TweetTableViewCell.identifier, for: indexPath) as? TweetTableViewCell else { return TweetTableViewCell() }
        
        return cell
    }
}

 

 

 

🟨 ProfileTableViewHeader.swfit 코드 구현

import UIKit

class ProfileTableViewHeader: UIView {
    
    
    private let joinDateLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Joined May 2024"
        label.textColor = .secondaryLabel
        label.font = .systemFont(ofSize: 14, weight: .regular)
        return label
    }()
    
    private let joinDateImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = UIImage(systemName: "calendar", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14))
        imageView.tintColor = .secondaryLabel
        return imageView
    }()
    
    private let userBioLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 3
        label.textColor = .label
        label.text = "iOS Developer"
        return label
    }()
        
    
    private let userNameLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "@Explorer"
        label.textColor = .secondaryLabel
        label.font = .systemFont(ofSize: 18, weight: .regular)
        return label
    }()
    
    private let displayNameLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Explorer"
        label.font = .systemFont(ofSize: 22, weight: .bold)
        return label
    }()
    
    
    private let profileAvatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 40
        imageView.layer.masksToBounds = true
        imageView.image = UIImage(named: "profile")
        imageView.backgroundColor = .systemYellow
        return imageView
    }()
    
    private let profileHeaderImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.image = UIImage(named: "header")
        imageView.clipsToBounds = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // backgroundColor = .systemBlue
        
        addSubview(profileHeaderImageView)
        addSubview(profileAvatarImageView)
        addSubview(displayNameLabel)
        addSubview(userNameLabel)
        addSubview(userBioLabel)
        addSubview(joinDateImageView)
        addSubview(joinDateLabel)
        
        configureConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func configureConstraints() {
        let profileHeaderImageViewConstraints = [
            profileHeaderImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
            profileHeaderImageView.topAnchor.constraint(equalTo: topAnchor),
            profileHeaderImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
            profileHeaderImageView.heightAnchor.constraint(equalToConstant: 100)
        ]
        
        
        let profileAvatarImageViewConstraints = [
            profileAvatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            profileAvatarImageView.centerYAnchor.constraint(equalTo: profileHeaderImageView.bottomAnchor, constant: 10),
            profileAvatarImageView.widthAnchor.constraint(equalToConstant: 80),
            profileAvatarImageView.heightAnchor.constraint(equalToConstant: 80)
        ]
        
        let displayNameLabelConstraints = [
            displayNameLabel.leadingAnchor.constraint(equalTo: profileAvatarImageView.leadingAnchor),
            displayNameLabel.topAnchor.constraint(equalTo: profileAvatarImageView.bottomAnchor, constant: 20)
        ]
        
        let userNameLabelConstraints = [
            userNameLabel.leadingAnchor.constraint(equalTo: displayNameLabel.leadingAnchor),
            userNameLabel.topAnchor.constraint(equalTo: displayNameLabel.bottomAnchor, constant: 5)
        ]
        
        let userBioLabelConstraints = [
            userBioLabel.leadingAnchor.constraint(equalTo: displayNameLabel.leadingAnchor),
            // userBioLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5),
            userBioLabel.topAnchor.constraint(equalTo: userNameLabel.bottomAnchor, constant: 5)
        ]
        
        let joinDateImageViewConstraints = [
            joinDateImageView.leadingAnchor.constraint(equalTo: displayNameLabel.leadingAnchor),
            joinDateImageView.topAnchor.constraint(equalTo: userBioLabel.bottomAnchor, constant: 5)
        ]
        
        let joinDateLabelConstraints = [
            joinDateLabel.leadingAnchor.constraint(equalTo: joinDateImageView.trailingAnchor, constant: 2),
            joinDateLabel.bottomAnchor.constraint(equalTo: joinDateImageView.bottomAnchor)
        ]
        
        NSLayoutConstraint.activate(profileHeaderImageViewConstraints)
        NSLayoutConstraint.activate(profileAvatarImageViewConstraints)
        NSLayoutConstraint.activate(displayNameLabelConstraints)
        NSLayoutConstraint.activate(userNameLabelConstraints)
        NSLayoutConstraint.activate(userBioLabelConstraints)
        NSLayoutConstraint.activate(joinDateImageViewConstraints)
        NSLayoutConstraint.activate(joinDateLabelConstraints)
    }
}

 

 

🟨 TIL

  • 어떤 문제가 있었는지?
    • 로고가 원하는 크기에 맞지 않게 출력됨 

내가 원한건 이게 아니다.
내가 원한거..

 

  • logo 이미지를 UIImageView 타입의 logoImageView에 담는다.
  • logoImageView를 navigationItem.titleView에 할당한다. 
  • 근데 이러면 로고가 네비게이션 부분을 벗어난다.
private func configureNavigationBar() {
    let size: CGFloat = 36
    let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: size, height: size))
    logoImageView.contentMode = .scaleAspectFill
    logoImageView.image = UIImage(named: "logo")

    navigationItem.titleView = logoImageView
}

 

 

  • 아래와 같이 코드를 설정하면 네비게이션 부분에 맞게 나오긴 하는데... 크기가 내가 처음 설정한 크기에 맞게 나오는게 아니라 비율에 따라 맞춰들어가기 때문에 안된다.
logoImageView.contentMode = .scaleAspectFit

 

 

  • 어떻게 해결했는지
    • UIView 타입의 middleView를 크기와 위치를 logoImageView와 동일하게 해서 생성
    • middleView에 logoImageView를 삽입
    • navigationItem의 TitleView에 middleView 할당
    • 단, logoImageView.contentMode = .scaleAspectFill 
private func configureNavigationBar() {
    let size: CGFloat = 36
    let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: size, height: size))
    logoImageView.contentMode = .scaleAspectFill
    logoImageView.image = UIImage(named: "logo")

    let middleView = UIView(frame: CGRect(x: 0, y: 0, width: size, height: size))
    middleView.addSubview(logoImageView)
    navigationItem.titleView = middleView

    ....

 

 

로고 이미지가 36에 맞게 나온 거 같다.?

(이미지 크기를 확인하고 싶은데.. 어떻게 하는거지..)

 

 

✅ 시스템 이미지 크기 조절 

imageView.image = UIImage(systemName: "calendar", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14))

 

 

https://youtu.be/scqLiVBWhg0?si=Ccs7EmZUFp6TsOK0