팀 소개 앱 개발 프로젝트.
스토리보드를 활용해서 두 개의 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 |