iOS/UIKIt

[ iOS/UIkit ] UITableView로 마이페이지 구현하기

개발하는 감자입니다 2024. 3. 6. 21:49
728x90

 

안녕하세요! 개발감자입니다.

오늘은 iOS 앱 개발에서 자주 사용되는 UI 구성 요소 중 하나인 테이블뷰를 이용해 마이페이지를 구현하는 방법에 대해 알아보려고 합니다. 이 글에서는 UIKit의 UITableView를 사용하여 마이페이지의 다양한 섹션과 옵션을 표시하는 방법을 함께 살펴보겠습니다. 시작하기에 앞서 오늘 설명할 코드의 결과물을 먼저 보여드리고 시작할게요!

이 결과물을 어떻게 만들었는지 코드를 보면서 설명해보도록 하겠습니다.

구현한 마이페이지 화면

 

목차는 아래와 같습니다.

1. MyPageViewController.swift

1) 기본 설정 및 초기화

2) UITableViewDataSource 구현

3) UITableViewDelegate 구현

 

2. MyPageTableViewCell.swift

1) 셀 구성요소

2) 프로필 정보 추가

1. MyPageViewController.swift

 

1. MyPageViewController.swift

MyPageViewController는 마이페이지의 주요 화면을 관리하는 뷰 컨트롤러입니다. 이 클래스는 UITableViewDataSource, UITableViewDelegate, 그리고 커스텀 프로토콜인 ProfileViewDelegate를 채택하여 구현됩니다.

//  MyPageViewController.swift
//  Money-Planner
//
//  Created by p_kxn_g on 1/30/24.
//
import Foundation
import UIKit


class MyPageViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, ProfileViewDelegate {
    var tempUserName : String = ""
    var tempProfileImage: UIImage?

    var user = User()
    // 테이블 뷰 데이터 소스
    let myPageData = [
        Section(title: "프로필", items: ["프로필"]),
        Section(title: "설정", items: ["알림 설정"]),
        Section(title: "앱 정보 및 문의", items: ["앱 버전", "약관 및 정책", "개인정보 처리 방침", "1:1 문의하기"]),
        Section(title: "계정", items: ["로그아웃", "탈퇴하기"])
    ]

    // UITableView 인스턴스
    let tableView: UITableView = {
        let table = UITableView()
        table.translatesAutoresizingMaskIntoConstraints = false
        table.separatorStyle = .none
        return table
    }()
  
    override func viewDidLoad() {
        super.viewDidLoad()
        tempUserName = user.userNameString
        navigationController?.isNavigationBarHidden = true // 네비게이션 바 숨김

        // 커스텀 UITableViewCell 등록
        tableView.register(MyPageTableViewCell.self, forCellReuseIdentifier: "myPageCell")


        // 테이블 뷰의 델리게이트와 데이터 소스 설정
        tableView.delegate = self
        tableView.dataSource = self

        // 테이블 뷰를 뷰 계층에 추가
        view.addSubview(tableView)

        // 테이블 뷰에 대한 제약 조건 설정
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }

    // MARK: - UITableViewDataSource 메서드

    func numberOfSections(in tableView: UITableView) -> Int {
        return myPageData.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myPageData[section].items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myPageCell", for: indexPath) as! MyPageTableViewCell
        let item = myPageData[indexPath.section].items[indexPath.row]
        var text = item
 
        if item == "알림 설정" {
            // 알림 설정인 경우 on or off 버튼 추가
            text = "ON"
            cell.optionalLabel.text = text
            
        }
        if item == "프로필"{
            text = "프로필 설정"
            cell.optionalLabel.text = text
            tempProfileImage = UIImage(named: "add-05")
            cell.addProfile(user.userNameString, image : tempProfileImage)

            // 프로필인 경우 프로필 띄우기
        }
        else{
            cell.textLabel?.text = item
            cell.textLabel?.font = UIFont.mpFont16M()

        }
        
            // 각 셀에 대한 추가 작업을 수행할 수 있습니다.
            return cell
        }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if myPageData[section].title == "프로필"{
            return nil // 부제목을 표시하지 않음
        }
        return myPageData[section].title
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let item = myPageData[indexPath.section].items[indexPath.row]
        if item == "프로필"{
            return 128.0 // 프로필인 경우 높이 120

        }else{
            return 60.0 //프로필이 아닌 경우 높이 60
        }
        
    }

    // MARK: - UITableViewDelegate 메서드

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        // 셀 선택 처리
        let selectedItem = myPageData[indexPath.section].items[indexPath.row]

        switch selectedItem {
        case "프로필":
            // 프로필 뷰로 이동
            print("프로필 선택됨")
            // 프로필 설정 화면으로 이동
            settingProfile()
            
        case "알림 설정":
            // 알림 설정 뷰로 이동
            print("알림 설정 선택됨")
        case "앱 버전":
            // 앱 버전 뷰로 이동
            print("앱 버전 선택됨")
        case "약관 및 정책":
            // 약관 및 정책 뷰로 이동
            print("약관 및 정책 선택됨")
        case "개인정보 처리 방침":
            // 개인정보 처리 방침 뷰로 이동
            print("개인정보 처리 방침 선택됨")
        case "1:1 문의하기":
            // 1:1 문의하기 뷰로 이동
            Ask()
            print("1:1 문의하기 선택됨")
        case "로그아웃":
            // 로그아웃 처리
            print("로그아웃 선택됨")
            // 로그아웃 모달로 이동
            let logoutVC = PopupViewController() // 로그아웃 완료 팝업 띄우기
            present(logoutVC, animated: true)
            
            
        case "탈퇴하기":
            // 계정 탈퇴 처리
            print("탈퇴하기 선택됨")
            Unregister()
        default:
            break
        }
    }

    // 섹션과 아이템을 나타내는 데이터 구조
    struct Section {
        var title: String
        var items: [String]
    }
    
    func profileNameChanged(_ userName: String, _ profileImage : UIImage?) {
        user.changeUserName(userName)
        tempUserName = userName
        tempProfileImage = profileImage // Set the profile image in your User model

        // Reload only the cell representing the profile
        if let indexPath = indexPathForProfileCell() {
            tableView.reloadRows(at: [indexPath], with: .none)
        }

        print("프로필 이름이 변경되었습니다")
        print(user.userNameString)
        view.layoutIfNeeded()
    }

    private func indexPathForProfileCell() -> IndexPath? {
        for (sectionIndex, section) in myPageData.enumerated() {
            if let rowIndex = section.items.firstIndex(of: "프로필") {
                return IndexPath(row: rowIndex, section: sectionIndex)
            }
        }
        return nil
    }
    
    func settingProfile() {
        let profileVC = ProfileViewController(tempUserName: tempUserName) // 프로필 설정 화면으로 이동
        profileVC.modalPresentationStyle = .fullScreen
        profileVC.delegate = self
        present(profileVC, animated: true)
    }
    func Ask(){
        let askVC = AskViewController() // 프로필 설정 화면으로 이동
        askVC.modalPresentationStyle = .fullScreen
        //askVC.delegate = self
        present(askVC, animated: true)
        
    }
    func Unregister(){
        let unregisterVC = UnregisterViewController() // 프로필 설정 화면으로 이동
        unregisterVC.modalPresentationStyle = .fullScreen
        //askVC.delegate = self
        present(unregisterVC, animated: true)
        
    }
}

 

1) 기본 설정 및 초기화

  • 사용자 정보와 마이페이지 데이터를 저장하기 위한 변수들이 선언되어 있습니다.
  • UITableView 인스턴스를 생성하고, 기본적인 스타일을 설정합니다. 테이블 뷰의 셀로는 MyPageTableViewCell이 사용됩니다.
  • viewDidLoad 메서드에서 테이블 뷰를 뷰 계층에 추가하고, 적절한 제약 조건을 설정하여 화면에 표시될 수 있도록 합니다.

2) UITableViewDataSource 구현

  • 섹션의 개수(numberOfSections)와 각 섹션의 행 수(numberOfRowsInSection)를 결정하기 위해 myPageData 배열을 참조합니다.
  • 각 셀의 내용은 tableView(_:cellForRowAt:) 메서드에서 구성됩니다. 여기서는 특정 아이템(예: "프로필", "알림 설정")에 따라 다른 UI 구성 요소를 셀에 추가하거나 변경합니다.

3) UITableViewDelegate 구현

  • 셀의 높이(heightForRowAt)는 "프로필" 아이템과 다른 아이템을 구분하여 다르게 설정합니다.
  • 사용자가 셀을 선택했을 때(didSelectRowAt)의 동작을 정의합니다. 예를 들어, "프로필"을 선택하면 프로필 설정 화면으로 이동합니다.

 

2.MyPageTableViewCell.swift

MyPageTableViewCell 클래스는 마이페이지의 각 항목을 나타내는 데 사용되는 커스텀 테이블 뷰 셀입니다.

//
//  cell.swift
//  Money-Planner
//
//  Created by p_kxn_g on 1/30/24.
//

import Foundation
import UIKit
class MyPageTableViewCell: UITableViewCell {
    let container : UIView = {
        let view = UIView()
        //view.backgroundColor = .red
        return view
    }()
    // 셀에 추가할 이미지 뷰
    let cellImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.image = UIImage(systemName:"chevron.forward")
        imageView.tintColor = .mpDarkGray

        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    let optionalLabel : MPLabel = {
        let label = MPLabel()
        label.font = UIFont.mpFont16M()
        label.textColor = .mpDarkGray
        label.text = ""
        return label
    }()

    let userName : MPLabel = {
        let label = MPLabel()
        label.font = UIFont.mpFont20B()
        label.textColor = .mpBlack
        return label
    }()
    let userImage : UIImageView = {
        let imageView = UIImageView()
        imageView.backgroundColor = .mpGypsumGray
        imageView.layer.cornerRadius = 32 //지름 64
        imageView.layer.masksToBounds = true
        imageView.contentMode = .scaleAspectFill
        //imageView.image = UIImage(systemName: "pencil")
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
        }()
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        // 셀에 이미지 뷰를 추가
        addSubview(container)
        container.addSubview(cellImageView)
        container.addSubview(optionalLabel)
        container.translatesAutoresizingMaskIntoConstraints = false
        cellImageView.translatesAutoresizingMaskIntoConstraints = false
        optionalLabel.translatesAutoresizingMaskIntoConstraints = false

        //addSubview(alarmSettingLabel)
        // 이미지 뷰에 대한 제약 조건 설정
        NSLayoutConstraint.activate([
            container.topAnchor.constraint(equalTo: topAnchor),
            container.trailingAnchor.constraint(equalTo: trailingAnchor,constant:-25),
            container.bottomAnchor.constraint(equalTo: bottomAnchor),
            container.widthAnchor.constraint(equalToConstant: 89),
            cellImageView.topAnchor.constraint(equalTo: container.topAnchor),
            cellImageView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            cellImageView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
            cellImageView.widthAnchor.constraint(equalToConstant: 8),// 이미지 뷰의 폭 조정
            optionalLabel.topAnchor.constraint(equalTo: container.topAnchor),
            optionalLabel.trailingAnchor.constraint(equalTo: cellImageView.leadingAnchor, constant: -13),
            optionalLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor),
            //alarmSettingLabel.heightAnchor.constraint(equalToConstant: 22),
            //larmSettingLabel.trailingAnchor.constraint(equalTo: cellImageView.leadingAnchor,constant:13),
            //alarmSettingLabel.widthAnchor.constraint(equalToConstant: 24) // 이미지 뷰의 폭 조정
            
        ])
    }
    func addProfile (_ name : String, image : UIImage?){
        let blank : UIView = {
            let view = UIView()
            view.backgroundColor = .mpGypsumGray
            return view
        }()
        userName.text = "\(name)님"
        userImage.image = image
        
        container.addSubview(userImage)
        container.addSubview(userName)
        container.addSubview(blank)

        userImage.translatesAutoresizingMaskIntoConstraints = false
        userName.translatesAutoresizingMaskIntoConstraints = false
        blank.translatesAutoresizingMaskIntoConstraints = false

        
        NSLayoutConstraint.activate([
            userImage.centerYAnchor.constraint(equalTo: centerYAnchor),
            userImage.leadingAnchor.constraint(equalTo: leadingAnchor,constant:16),
            userImage.widthAnchor.constraint(equalToConstant: 64),
            userImage.heightAnchor.constraint(equalToConstant: 64),
        // 유저 네임에 해당하는 라벨 오른쪽으로 옮기기
            userName.centerYAnchor.constraint(equalTo: centerYAnchor),
            userName.leadingAnchor.constraint(equalTo:userImage.trailingAnchor,constant:24),
            blank.leadingAnchor.constraint(equalTo: leadingAnchor),
            blank.trailingAnchor.constraint(equalTo: trailingAnchor),
            blank.heightAnchor.constraint(equalToConstant: 8),
            blank.bottomAnchor.constraint(equalTo: bottomAnchor),

        ])
        
    }
    func setUserName(_ name: String) { // 사용자 이름 업데이트
           userName.text = "\(name)님"
       }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

1) 셀 구성요소

  • 셀에는 사용자 프로필 이미지, 이름 라벨, 선택적 라벨, 이미지 뷰 등 여러 UI 컴포넌트가 포함됩니다.
  • 각 컴포넌트는 Auto Layout 제약 조건을 사용하여 적절한 위치와 크기를 가지도록 설정됩니다.

2) 프로필 정보 추가

  • addProfile 메서드를 통해 셀에 프로필 이미지와 사용자 이름을 추가하고 배치합니다. 이는 "프로필" 아이템에 대한 셀 구성에 사용됩니다.

 

이렇게 MyPageViewController와 MyPageTableViewCell를 통해 마이페이지의 UI를 구성하고, 각 섹션과 아이템에 대한 상호작용을 정의할 수 있습니다. UIKit의 UITableView를 사용하면 다양한 설정과 정보를 표시하는 마이페이지를 효과적으로 구현할 수 있습니다. 이 구현 방법은 iOS 앱 개발에서 자주 사용되는 패턴 중 하나이며, 커스터마이징하여 다양한 요구 사항에 맞게 조정할 수 있습니다.

728x90
반응형