iOS

[iOS] 2025.03.05 팀프로젝트(3)

ioskkt 2025. 3. 5. 21:24

팀 소개 앱 개발 프로젝트.

스토리보드를 활용해서 두 개의 ViewController를 이용해서 만들어봄.

*깔끔하게 만들려면 더 늘려도 되겠다는 생각이 들었음.(계속 공부해나가야 할 부분)

//ViewController 화면

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var firstTableView: UITableView!
    @IBOutlet weak var secondTableView: UITableView!

    let teamCharacteristic = [
        ("우리팀의 특징", "개인의 성장과 팀의 발전을 함께 추구합니다."),
        ("궁극적인 목표", "하루 1%의 변화가 모이면, 1년 후 37배의 성장이 됩니다."),
        ("우리팀의 약속", "개발 과정에서 정직하고 투명한 커뮤니케이션을 유지합니다.")]
    let teammate: [String] = ["🙋🏻‍♀️박혜민", "🙆🏻‍♂️김기태", "🙋🏻‍♂️김형윤", "💁🏻‍♂️변준영"]
    let MBTI: [String] = ["ESTJ", "INFP", "ISTP", "INTJ"]

    // 각 팀원의 강점, 스타일, 주소 정보, 이미지
    let strength: [String: String] = [
        "🙋🏻‍♀️박혜민": "솔직한 커뮤니케이션",
        "🙆🏻‍♂️김기태": "최신 기술 트렌드에 대한 관심과 빠른 학습 능력",
        "🙋🏻‍♂️김형윤": "논리적으로 사고하고 분석하는걸 좋아함",
        "💁🏻‍♂️변준영": "이전 경력을 통해 폭 넓은 시야와 다양한 관점을 갖춤"]
    
    let style: [String: String] = [
        "🙋🏻‍♀️박혜민": "빠른 문제 해결을 위해 자주 소통하는 편",
        "🙆🏻‍♂️김기태": "문제 발생 시 원인을 분석하고 해결책을 공유",
        "🙋🏻‍♂️김형윤": "상대방의 스타일에 맞춰가는 편",
        "💁🏻‍♂️변준영": "적극적인 질문을 통해 문제를 발견하고 해결"]
    
    let adress: [String: String] = [
        "🙋🏻‍♀️박혜민": "https://velog.io/@hmpark15/posts",
        "🙆🏻‍♂️김기태": "https://ioskkt.tistory.com/",
        "🙋🏻‍♂️김형윤": "https://y-oon.tistory.com/",
        "💁🏻‍♂️변준영": "https://www.notion.so/CamCoder-18fa28cef94e8022aa29f8bed90e96ef?pvs=4"]
    
    let teammateImages: [String: String] = [
        "🙋🏻‍♀️박혜민": "박혜민.jpg",
        "🙆🏻‍♂️김기태": "김기태.jpg",
        "🙋🏻‍♂️김형윤": "김형윤.jpg",
        "💁🏻‍♂️변준영": "변준영.jpg"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        firstTableView.delegate = self
        firstTableView.dataSource = self
        
        secondTableView.delegate = self
        secondTableView.dataSource = self
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == firstTableView {
            return teamCharacteristic.count
        } else if tableView == secondTableView {
            return teammate.count
        }
        return 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if tableView == firstTableView {
            let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath)
            cell.textLabel?.text = teamCharacteristic[indexPath.row].0
            cell.detailTextLabel?.text = teamCharacteristic[indexPath.row].1
            return cell
        } else if tableView == secondTableView {
            let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath)

            // 이름과 이모지 설정
            let fullName = "\(teammate[indexPath.row])"
            cell.textLabel?.text = fullName

            // MBTI 레이블 추가
            if let mbtiLabel = cell.viewWithTag(100) as? UILabel {
                let mbti = MBTI[indexPath.row]
                mbtiLabel.text = mbti

                // MBTI에 따라 색상 설정
                switch mbti {
                case "ESTJ":
                    mbtiLabel.backgroundColor = UIColor(hex: "#FCE6E6")
                case "INFP":
                    mbtiLabel.backgroundColor = UIColor(hex: "#EBFCE6")
                case "ISTP":
                    mbtiLabel.backgroundColor = UIColor(hex: "#E6F4FC")
                case "INTJ":
                    mbtiLabel.backgroundColor = UIColor(hex: "#D6D8FD")
                default:
                    mbtiLabel.backgroundColor = .gray
                }

                mbtiLabel.layer.cornerRadius = 10 // 원하는 크기로 조정
                mbtiLabel.layer.masksToBounds = true
                
                mbtiLabel.frame.size = CGSize(width: 60, height: 20)
                mbtiLabel.textAlignment = .center
                
                
            }

            return cell
        }
        
        return UITableViewCell()
    }

    // 셀 선택 시 데이터 전달 및 segue 실행
    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
        if identifier == "ShowTeammateDetail" {
            return false // 🚀 자동 실행 방지
        }
        return true
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if tableView == secondTableView {
            let selectedTeammate = teammate[indexPath.row]

            // 팀원의 정보 가져오기
            if let strengthValue = strength[selectedTeammate],
                       let styleValue = style[selectedTeammate],
                       let adressValue = adress[selectedTeammate],
                       let imageName = teammateImages[selectedTeammate] {

                let dataToSend: [String: Any] = [
                    "name": selectedTeammate,
                    "strength": strengthValue,
                    "style": styleValue,
                    "adress": adressValue,
                    "imageName": imageName
                ]

                // 데이터 전달 후 segue 실행
                performSegue(withIdentifier: "ShowTeammateDetail", sender: dataToSend)
            }

            tableView.deselectRow(at: indexPath, animated: true)
        }
    }

    // segue에서 데이터 받기
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowTeammateDetail" {
            if let teammateDetailVC = segue.destination as? TeammateDetailViewController {
                if let data = sender as? [String: Any] {
                    teammateDetailVC.teammateName = data["name"] as? String ?? "이름 없음"
                    teammateDetailVC.teammateStrength = data["strength"] as? String ?? "강점 없음"
                    teammateDetailVC.teammateStyle = data["style"] as? String ?? "스타일 없음"
                    teammateDetailVC.teammateAdress = data["adress"] as? String ?? "주소 없음"
                    teammateDetailVC.teammateImageName = data["imageName"] as? String ?? ""
                }
            }
        }
    }
}

// UIColor 확장을 작성해서 hex 값을 처리할 수 있도록 합니다.
extension UIColor {
    convenience init(hex: String) {
        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)

        // '#' 제거
        if hexSanitized.hasPrefix("#") {
            hexSanitized = String(hexSanitized.dropFirst())
        }

        // HEX 코드의 길이가 6인지 확인
        if hexSanitized.count == 6 {
            let scanner = Scanner(string: hexSanitized)
            var rgb: UInt64 = 0
            scanner.scanHexInt64(&rgb)

            self.init(
                red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
                green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
                blue: CGFloat(rgb & 0x0000FF) / 255.0,
                alpha: 1.0
            )
        } else {
            self.init(white: 0.0, alpha: 1.0) // 기본값은 검정색
        }
    }
}

 

//TeammateDetailViewController 화면

import UIKit

class TeammateDetailViewController: UIViewController {

    // 변수 선언
    var teammateName: String?
    var teammateStrength: String?
    var teammateStyle: String?
    var teammateAdress: String?
    var teammateImageName: String?

    
    @IBOutlet weak var nameLabel: UILabel!
    
    @IBOutlet weak var strengthLabel: UILabel!
    @IBOutlet weak var styleLabel: UILabel!
    @IBOutlet weak var adressLabel: UILabel!
    @IBOutlet weak var profileImageView: UIImageView!
    
    

    override func viewDidLoad() {
        super.viewDidLoad()

        // 팀원 이름, 사진, 정보 설정
        if let name = teammateName {
            nameLabel.text = name
        }
        if let strength = teammateStrength {
            strengthLabel.text = strength
        }
        if let style = teammateStyle {
            styleLabel.text = style
        }
        if let adress = teammateAdress {
            adressLabel.text = adress
        }
        if let imageName = teammateImageName {
            profileImageView.image = UIImage(named: imageName)
        }
    }
}

 

어제까지 계속 모달이 실행이 안됐었으나, 문제의 이유는 모달의 방향이었음.

하위페이지의 Label을 상위페이지로 연결하려고 하다보니 연결할 때 부터 문제가 있었고, 코드와 연결할 때는 Identifier를 일치시켜줘야 한다는 것을 뒤늦게 알게되었음.

며칠 코드를 붙들고 있어보니 Identifier, Tag 등 식별할 수 있는 매체가 있어야 코드가 인식할 수 있다는 것을 확인할 수 있었음.

 

한 줄씩 리뷰.

import UIKit

class ViewController: UIViewController {

UIKit 프레임 워크를 사용함. 스토리보드를 활용하여 개발하였으며 UI 요소들을 사용함.

UIViewController를 상속받아 앱의 메인화면을 구성하는 역할을 함.

UIViewController 프로토콜을 채택한 것.

 

    @IBOutlet weak var firstTableView: UITableView!
    @IBOutlet weak var secondTableView: UITableView!

IBOutlet을 통해 UI요소를 연결함.

Interface Builder에서 두 개의 테이블 뷰를 연결함.

첫 번째 테이블은 팀의 특징, 두 번째 테이블은 팀원 정보를 표시하고자 나눔.

 

    let teamCharacteristic = [
        ("우리팀의 특징", "개인의 성장과 팀의 발전을 함께 추구합니다."),
        ("궁극적인 목표", "하루 1%의 변화가 모이면, 1년 후 37배의 성장이 됩니다."),
        ("우리팀의 약속", "개발 과정에서 정직하고 투명한 커뮤니케이션을 유지합니다.")]
        
    let teammate: [String] = ["🙋🏻‍♀️박혜민", "🙆🏻‍♂️김기태", "🙋🏻‍♂️김형윤", "💁🏻‍♂️변준영"]
    let MBTI: [String] = ["ESTJ", "INFP", "ISTP", "INTJ"]

팀의 특징을 배열 형태로 저장함. 첫 번째 테이블 뷰 셀의 스타일을 subtitle로 지정했기 때문에 (제목, 부제목)의 형태로 저장함.

* 여러 개의 값을 하나의 그룹으로 묶는 데이터구조를 튜플(Tuple)이라고 함.

이모지+팀원이름, mbti를 배열구조로 저장

 

let strength: [String: String] = [
        "🙋🏻‍♀️박혜민": "솔직한 커뮤니케이션",
        "🙆🏻‍♂️김기태": "최신 기술 트렌드에 대한 관심과 빠른 학습 능력",
        "🙋🏻‍♂️김형윤": "논리적으로 사고하고 분석하는걸 좋아함",
        "💁🏻‍♂️변준영": "이전 경력을 통해 폭 넓은 시야와 다양한 관점을 갖춤"]
    
    let style: [String: String] = [
        "🙋🏻‍♀️박혜민": "빠른 문제 해결을 위해 자주 소통하는 편",
        "🙆🏻‍♂️김기태": "문제 발생 시 원인을 분석하고 해결책을 공유",
        "🙋🏻‍♂️김형윤": "상대방의 스타일에 맞춰가는 편",
        "💁🏻‍♂️변준영": "적극적인 질문을 통해 문제를 발견하고 해결"]
    
    let adress: [String: String] = [
        "🙋🏻‍♀️박혜민": "https://velog.io/@hmpark15/posts",
        "🙆🏻‍♂️김기태": "https://ioskkt.tistory.com/",
        "🙋🏻‍♂️김형윤": "https://y-oon.tistory.com/",
        "💁🏻‍♂️변준영": "https://www.notion.so/CamCoder-18fa28cef94e8022aa29f8bed90e96ef?pvs=4"]
    
    let teammateImages: [String: String] = [
        "🙋🏻‍♀️박혜민": "박혜민.jpg",
        "🙆🏻‍♂️김기태": "김기태.jpg",
        "🙋🏻‍♂️김형윤": "김형윤.jpg",
        "💁🏻‍♂️변준영": "변준영.jpg"]

강점, 스타일, 블로그주소, 이미지를 [키:값] 형식을 통해 딕셔너리 타입으로 저장.

 

override func viewDidLoad() {
        super.viewDidLoad()
        
        firstTableView.delegate = self
        firstTableView.dataSource = self
        
        secondTableView.delegate = self
        secondTableView.dataSource = self
    }

UIViewController를 상속받은 ViewController가 

UIViewController에서 제공하는 viewDidLoad() 메서드를 오버라이드(재정의)하여 뷰가 로드될 때 필요한 초기작업을 할 수 있도록 함.

 

첫 번째 테이블뷰와 두 번째 테이블 뷰를 self로 설정하여

ViewController가 UITableViewDelegate 및 UITableViewDataSource의 역할을 하도록 지정함.

쉽게 말해 각 역할을 ViewController가 대신 하는것.

 

extension ViewController: UITableViewDelegate, UITableViewDataSource

ViewController가 UITableViewDelegate, UITableViewDataSource 프로토콜을 채택.

즉 UIViewController, UITableViewDelegate, UITableViewDataSource 와 같은 프로토콜에 정의되어 있는 기능들을 ViewController에서 쓰겠다는 의미.

 

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == firstTableView {
            return teamCharacteristic.count
        } else if tableView == secondTableView {
            return teammate.count
        }
        return 0
    }

UITableViewDataSource 프로토콜의 필수 메서드인 func tableView(numberOfRowsInSection) 의 구현.

테이블 뷰에 구현할 행의 갯수를 반환하는 역할을 함.

_(밑줄)의 의미는 함수를 호출할 때 외부 매개변수를 호출하지 않겠다는 의미.

 

만약 테이블뷰가 첫 번째 테이블 뷰라면, 팀 특징 배열의 갯수(3개) 만큼 행의 갯수를 반환함.

테이블 뷰가 두 번째 테이블 뷰라면, 팀원 배열의 갯수(4개) 만큼 행의 갯수를 반환함.

이외의 경우는 0을 반환(표시할 행이 없다는 것을 의미)

 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if tableView == firstTableView {
            let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath)
            cell.textLabel?.text = teamCharacteristic[indexPath.row].0
            cell.detailTextLabel?.text = teamCharacteristic[indexPath.row].1
            return cell
        } else if tableView == secondTableView {
            let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath)
                       
            let fullName = "\(teammate[indexPath.row])"
            cell.textLabel?.text = fullName

UITableViewDataSource 프로토콜의 필수 메서드인 tableView(_:cellForRowAt:)의 구현.

테이블 뷰의 특정 위치(indexPath)에 표시할 셀을 생성하고 반환하는 역할을 함.

 

만약 테이블 뷰가 첫 번째 테이블 뷰라면, 테이블 뷰에서 재사용 가능한 셀을 가져옴.

여기서 재사용이란 사용자가 스크롤을 통해서 화면에서 사라진 경우 그 셀을 큐에 저장했다가, 재사용이 가능할 때 다시 사용하는 것을 의미.

또한 재사용 가능한 셀이 없을 때, 새로운 셀을 생성함.

여기서는 스크롤이 발생하지 않으므로 초기 로딩 시 Array의 갯수만큼 셀을 생성하고 이후에는 재사용이 발생하지 않음.

 

해당 테이블 뷰는 서브타이틀 형식으로 UITableViewCell에서 사용되는 고유한 프로퍼티인 textLabel, detailTextLabel을 사용함.

 

만약 테이블 뷰가 두 번재 테이블 뷰라면, 테이블 뷰에서 재사용 가능한 셀을 가져오거나, 셀을 새롭게 생성함.

SecondCell이라는 식별자를 가진 셀을 indexPath 위치에 생성함.

생성된 셀에 fullName의 규칙에 따라 내용을 작성함.

 

 

 

'iOS' 카테고리의 다른 글

[iOS] 2025.03.24 클래스와 구조체  (0) 2025.03.24
[iOS] 2025.03.20 과제 트러블 슈팅  (0) 2025.03.20
[iOS] 2025.03.04 팀프로젝트(2)  (0) 2025.03.04
[iOS] 2025.03.03 팀프로젝트(1)  (1) 2025.03.03
[iOS] 2025.02.26 Combine  (0) 2025.02.26