728x90
728x90
SMALL
아주 유용하게 쓰이는 TableView!
이래저래 필요한 부분을 생각하다보니 눌렀을 때 아래로 관련 행이 하나 더 추가되는 확장 가능한 Expandable Cell이 만들고 싶어졌다!
그런데 아무리 뒤져바도 섹션(Section)헤더를 쓰지 않고 한 섹션 당 한 행이 존재하는 구현 방법들이 나올뿐..
한 섹션 당 여러 행이 존재하여 헤더를 사용하는 경우가 나오지 않아 직접 만들어 보았다.
Github
결과화면
원하는 이름 셀을 클릭하면 아래 확장 셀이 열리면서 핸드폰 번호를 확인 할 수 있다
데이터 구조
- UserData: 이름, 분류, 핸드폰 번호를 저장하는 구조체
struct UserData {
var name:String
var category:Category
var phone:String
}
- Category: 가족, 친구, 동료 등 관계를 규정하는 분류 체계
enum Category:String {
case Family = "Family"
case Friend = "Friend"
case Coworker = "Coworker"
}
기본 세팅
프로젝트 생성 후 TableView를 생성하고 Auto Layout을 잡아준 뒤,
설정에서 Style을 Groped로 지정한다. (원하는 스타일로 설정해도 무방)
Xib를 이용한 셀 만들기
- Xib, Swift 파일 생성
1. 파일 추가 버튼 선택
2. Cocoa Touch Class 클릭
3. 세부 설정
Class: 원하는 클래스명
Subclass of: UITableViewCell 설정
Language: Swift
4. Next 클릭
5. 같은 이름의 Storyboard, Swift 파일이 1개씩 생성됨
- Cell Storyboard
1. 일반 셀, 확장 셀 2가지를 구현해야함
2. View를 두 개 추가하여 Cell 전체로 레이아웃을 설정한다
3. 각 뷰를 원하는 대로 설정한다
4. 추후 isHidden을 이용하여 View를 변경한다
- 코드 구현
class ExpandableCell: UITableViewCell {
// 각각의 셀 뷰
@IBOutlet weak var defaultView: UIView!
@IBOutlet weak var ExpandedView: UIView!
//
@IBOutlet weak var labelName: UILabel!
@IBOutlet weak var btnArrow: UIButton!
@IBOutlet weak var labelPhone: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
initUI()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func initUI() {
// 폰트 및 색상 설정
labelName.font = UIFont.systemFont(ofSize: 15)
labelName.textColor = .black
labelPhone.font = UIFont.systemFont(ofSize: 15)
labelPhone.textColor = .black
// 화살표 이미지 및 기타 설정
btnArrow.setImage(UIImage(systemName: "chevron.down"), for: .normal)
btnArrow.tintColor = .black
btnArrow.contentMode = .center
btnArrow.isUserInteractionEnabled = false
}
func inputCell(isExpand:Bool, name:String, phone:String) {
// 셀 종류에 따른 컨트롤
controllCell(isExpand: isExpand)
// 텍스트 입력
labelName.text = name
labelPhone.text = phone
}
private func controllCell(isExpand:Bool) {
defaultView.isHidden = isExpand
ExpandedView.isHidden = !isExpand
}
}
- awakFromNib() : Cell이 생성될 때 시작됨
- setSelected() : Cell이 클릭되었을 때 실행됨
- initUI() : 셀이 생성될 때 기본적으로 UI 세팅을 진행한다
- inputCell() : 셀에 값을 전달하여 설정할 때 사용
구현하기
- 기본 세팅
class ViewController: UIViewController {
// TableView
@IBOutlet weak var phoneTable: UITableView!
// 전화번호부 데이터
let dataList:[UserData] = [
UserData(name: "김하나", category: .Friend, phone: "010-1111-1111"),
UserData(name: "이두울", category: .Family, phone: "010-2222-2222"),
UserData(name: "박세엣", category: .Coworker, phone: "010-3333-3333"),
UserData(name: "윤네엣", category: .Friend, phone: "010-4444-4444"),
UserData(name: "김다섯", category: .Family, phone: "010-5555-5555"),
UserData(name: "이여섯", category: .Friend, phone: "010-6666-6666"),
UserData(name: "박일곱", category: .Family, phone: "010-7777-7777"),
UserData(name: "김여덟", category: .Friend, phone: "010-8888-8888"),
UserData(name: "이아홉", category: .Coworker, phone: "010-9999-9999"),
UserData(name: "박여얼", category: .Friend, phone: "010-0000-0000")
]
// 카테고리 데이터
let categoryList:[Category] = [
.Family,
.Friend,
.Coworker
]
// 열린 셀 정보
var openedIndex:IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
// TableView 설정
phoneTable.dataSource = self
phoneTable.delegate = self
// Custom Cell 등록
registerCell()
// TableView UI 세팅
phoneTable.separatorInsetReference = .fromCellEdges
phoneTable.separatorInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
phoneTable.sectionHeaderTopPadding = 18
}
private func registerCell() {
// Nib 등록
phoneTable.register(UINib(nibName: "ExpandableCell", bundle: nil), forCellReuseIdentifier: "ExpandableCell")
}
}
- dataList, categoryList : 테이블 관련 데이터
- openedIndex : 유저가 클릭하여 열린 셀이 어떤 셀인지에 대한 데이터를 저장
- registerCell() : 이전에 만든 Cell의 등록을 위한 작업
- 설명
- Delegate &DataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
// 섹션 수
func numberOfSections(in: UITableView) -> Int {
return categoryList.count
}
// 섹션 당 행 수
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let category = categoryList[section]
// 해당 카테고리에 부합하는 행의 수
let count = dataList.filter({$0.category == category}).count
// 열린 셀이 있을 경우
if let openedIndex = openedIndex {
// 열린 셀의 섹션에 해당하는 경우엔 행을 하나 더 추가한다
return openedIndex.section == section ? count + 1 : count
}
return count
}
// 셀 정보
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell", for: indexPath) as? ExpandableCell else {
return UITableViewCell()
}
// 셀 설정을 위한 인덱스
var index = indexPath
var isExpand = false
// 셀 상태 체크
if let openedIndex = openedIndex { // 열려 있는 셀이 존재
if index.section == openedIndex.section && index.row > openedIndex.row {
if index.row == openedIndex.row + 1 {
isExpand = true
}
index = IndexPath(row: index.row-1, section: index.section)
}
}
let category = categoryList[index.section]
let item = dataList.filter({ $0.category == category })[index.row]
cell.inputCell(isExpand: isExpand, name: item.name, phone: item.phone)
return cell
}
// 행 선택 시 이벤트
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//선택 된 셀을 열린 인덱스로 지정
openedIndex = openedIndex == nil ? indexPath : nil
tableView.reloadData()
}
//MARK: - Size
// 행 높이
func tableView(_ tableView: UITableView, heightForRowAt: IndexPath) -> CGFloat {
return 45
}
// 헤더 높이
func tableView(_ tableView: UITableView, heightForHeaderInSection: Int) -> CGFloat {
return 60
}
//푸터 높이
func tableView(_ tableView: UITableView, heightForFooterInSection: Int) -> CGFloat {
return 18
}
//MARK: - Custom
// 커스텀 헤더
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 60))
headerView.backgroundColor = .lightGray.withAlphaComponent(0.1)
let labelHeader = UILabel(frame: CGRect(x: 18, y: 15, width: 300, height: 45))
let category = categoryList[section]
labelHeader.text = category.rawValue
labelHeader.font = UIFont.boldSystemFont(ofSize: 18)
labelHeader.textColor = .black
headerView.addSubview(labelHeader)
return headerView
}
//커스텀 푸터
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 18))
let lineView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 9))
footerView.addSubview(lineView)
lineView.backgroundColor = .lightGray.withAlphaComponent(0.1)
return footerView
}
}
- 카테고리 수 대로 섹션을 생성한다
- 카테고리에 해당하는 데이터들을 하위 행으로 추가한다
- 셀을 클릭하는 경우 해당 셀을 openedIndex로 지정한다
- 셀이 닫힌 경우에는 따로 처리할 일이 없으나, 셀이 열려 있을 경우 열린 셀과 [같은] 섹션이며, [이후] 행일 경우 행의 숫자를 -1하여 진행한다.
- (셀이 열려 있을 경우) 열린 셀과 [같은] 섹션이며, [바로 다음] 행일 경우 해당 셀은 확장된 셀(Expanded Cell)이다
TableViewCell 이외에도 Xib를 이용하면 다양한 UI를 편하게 사용할 수 있는데,
추후 그 부분에 대한 포스팅도 진행해보아야겠다.
728x90
728x90
LIST
'EXPERIENCE > iOS' 카테고리의 다른 글
[Xcode/iOS] Swift 구글(Google)로그인 스토리보드(StoryBoard)로 구현하고 정보 가져오기 (0) | 2023.01.26 |
---|---|
[Xcode/iOS] CocoaPods 설치 및 Podfile 명령어 정리 (0) | 2023.01.26 |
[Xcode/iOS] Swift 동적으로 제약조건(constraints) 또는 Auto Layout 변경하는 법 (with. Programmatically) (0) | 2023.01.09 |
[Xcode/iOS] iOS 업데이트에 따른 새로운 Xcode 버전 다운받기 (0) | 2023.01.09 |
[Xcode/iOS] Swift 앱스토어(AppStore) 인앱결제(In-App Purchase) 구현하기 (with. Sandbox 테스트) (5) | 2023.01.05 |