세그는 스토리보드에서 뷰 컨트롤러 사이의 화면전환을 위해 사용하는 객체로 별도의 코드 없이도 스토리보드에서 세그를 연결하여 뷰 컨트롤러 사이의 화면전환을 구현할 수 있다.
UIStoryboardSegue 클래스
UIStoryboardSegue 클래스는 UIKit에서 사용할 수 있는 표준 화면전환을 위한 프로퍼티와 메서드를 포함하고 있다. 커스텀 전환을 정의하기 위해 서브클래스를 구현해서 사용할 수도 있으며 필요에 따라서 UIViewController의 performSegue(withIdentifier:sender:)메서드를 사용하여 세그 객체를 코드로 실행할 수 있다.
세그(Segue)객체는 뷰 컨트롤러의 뷰 전환과 관련된 정보를 가지고 있다. 세그는 뷰 컨트롤러의 뷰를 다른 뷰 컨트롤러의 뷰로 전환할 때 뷰 컨트롤러의 prepare(for:sender:) 메서드를 사용하여 새로 보여지는 뷰 컨트롤러에 데이터를 전달할 수 있다.
주요 프로퍼티
var source: UIViewController : 세그에 전환을 요청하는 뷰 컨트롤러
var destination: UIViewController : 전환될 뷰 컨트롤러
var identifier: String? : 세그 객체의 식별자
주요 메서드
func perform() : 뷰 컨트롤러의 전환을 수행
인터페이스 빌더의 세그 연결
1.스토리보드에서 전환될 뷰 컨트롤러를 객체 라이브러리로부터 드래그 앤 드롭으로 추가
2.기존 뷰 컨트롤러의 뷰와 구분하기 위하여 새롭게 생성된 뷰 컨트롤러의 뷰를 선택하여 배경색을 변경
3.첫 번째 뷰 컨트롤러의 뷰 위에 객체 라이브러리로부터 버튼을 추가하고 역할을 알려주기 위하여 ‘Show blue view controller’로 title을 변경
4.버튼에서부터 키보드의 control키를 누른 상태로 드래그하여 전환될 뷰 컨트롤러에 드롭하여 연결
5.세그의 종류 중 Show를 선택(Show 세그는 iOS에서 현재 기기나 화면 상태에서 가장 적절한 화면전환 방식을 판단하여 화면을 전환)
위 그림을 보면 테이블뷰에는 여러 셀이 존재하고 있음을 볼 수 있다. 처음 왼쪽 그림을 보면 첫번째 셀이 맨 위에 있을 것이고 사용자가 더 아래의 셀을 보고싶다면 스크롤을 해서 화면을 올릴 것이고 그렇게 된다면 첫번째 셀은 화면에서 사라지고 왼쪽의 화면이 보이게 될 것이다. 이떄 화면에서 사라지게 되면 해당 셀은 Queue 에 가게 된다. (재사용 큐-재사용을 대기) 그러다가 밑에서 셀이 더 필요하게 되면 재사용 대기중인 셀이 그 뒤에 붙게된다. 즉, 화면 바깥으로 나간 셀은 다시 재사용이 필요해서 아래로 붙게 되기에 우리가 위에서 사용했던 dequeueReusableCell 는 이를 의미한다. (큐에 들어갔다가 다시 큐에서 나오는 것)
이를 확인해보기 위해 간단하게 코드를 작성해봅시다.
functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{ifindexPath.section<2{letcell:UITableViewCell=tableView.dequeueReusableCell(withIdentifier:self.cellIdentifier,for:indexPath)lettext:String=indexPath.section==0?korean[indexPath.row]:english[indexPath.row]cell.textLabel?.text=textifindexPath.row==1{cell.backgroundColor=UIColor.red}else{// 해당 코드가 없으면 재사용되는 코드 모두가 빨간색이 되어버린다cell.backgroundColor=UIColor.white}returncell}else{letcell:CustomTableViewCell=tableView.dequeueReusableCell(withIdentifier:self.customCellIdentifier,for:indexPath)as!CustomTableViewCellcell.leftLabel.text=self.dateFormatter.string(from:self.dates[indexPath.row])cell.righLable.text=self.timeFormatter.string(from:self.dates[indexPath.row])returncell}}
그런데 우리가 셀을 재사용하지 않는다면 어떻게 될까? > 코드를 작성해보자.
functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{ifindexPath.section<2{letcell:UITableViewCell=UITableViewCell()// 셀을 하나 생성lettext:String=indexPath.section==0?korean[indexPath.row]:english[indexPath.row]cell.textLabel?.text=textifindexPath.row==1{cell.backgroundColor=UIColor.red}else{cell.backgroundColor=UIColor.white}returncell}else{letcell:CustomTableViewCell=tableView.dequeueReusableCell(withIdentifier:self.customCellIdentifier,for:indexPath)as!CustomTableViewCellcell.leftLabel.text=self.dateFormatter.string(from:self.dates[indexPath.row])cell.righLable.text=self.timeFormatter.string(from:self.dates[indexPath.row])returncell}}
이렇게 되면 테이블 뷰의 로우가 정말 많은 숫자가 된다면 셀을 그에 맞게 만들어줘야 하기에 메모리의 낭비가 굉장히 심해질 것이다. 즉 화면에서 벗어난 셀은 큐에 담아두고 쓰고 하는 방식을 채택하는 것이 옳다.
iOS 기기는 한정된 메모리를 가지고 애플리케이션을 구동한다.
만약 사용자에게 보여주고 싶은 데이터가 매우 많고, 데이터의 양만큼 많은 뷰가 필요하다면 어떻게해야할까?
화면에 표시할 수 있는 뷰의 개수는 한정되어 있지만, 표현해야 하는 데이터가 많은 경우 반복된 뷰를 생성하기보다는 뷰를 재사용할 수 있다. 사용할 수 있는 메모리가 작아서 데이터의 양만큼 많은 뷰를 생성하는 것은 메모리를 많이 낭비할 수밖에 없기 때문이기에 뷰를 재사용함으로써 메모리를 절약하고 성능 또한 향상할 수 있다.
재사용의 대표적인 예
iOS 환경에서 뷰를 재사용하는 대표적인 예로 UITableViewCell, UICollectionViewCell 등이 있다.
UITableViewCell : UITableView의 셀
UICollectionViewCell : UICollectionView의 셀
재사용 원리
테이블뷰 및 컬렉션뷰에서 셀을 표시하기 위해 데이터 소스에 뷰(셀) 인스턴스를 요청
데이터 소스는 요청마다 새로운 셀을 만드는 대신 재사용 큐 (Reuse Queue)에 재사용을 위해 대기하고있는 셀이 있는지 확인 후 있으면 그 셀에 새로운 데이터를 설정하고, 없으면 새로운 셀을 생성
이제 데이터를 동적으로도 받아오는 방법을 익혀보았으니, 셀을 커스터마이징을 해보도록 하자!
main.storyboard 로 가봅시다.
tableViewCell 을 하나 추가해준다.
style: Custom 으로 설정해준다.
Identifier: customCell 로 지정해준다.
해당 셀 위에 레이블 두개를 추가해준다.
셀의 인스턴스 클래스를 만들기 위해 코코아터치 클래스를 생성해준다.
각각의 레이블이 행할 outlet을 만들어준다.
importUIKitclassCustomTableViewCell:UITableViewCell{@IBOutletvarleftLabel:UILabel!@IBOutletvarrighLable:UILabel!overridefuncawakeFromNib(){super.awakeFromNib()// Initialization code}overridefuncsetSelected(_selected:Bool,animated:Bool){super.setSelected(selected,animated:animated)// Configure the view for the selected state}}
그리고 다시 main.storyboard로 이동합니다. > 각각의 커스텀셀의 인터페이스를 완성해봅시다.
이를 실제로 테이블뷰에서 사용하기 위해서는 viewController로 이동!
importUIKitclassViewController:UIViewController,UITableViewDelegate,UITableViewDataSource{@IBOutletweakvartableView:UITableView!letcellIdentifier:String="cell"letcustomCellIdentifier:String="customCell"letkorean:[String]=["가","나","다","라"]letenglish:[String]=["a","b","c","d"]vardates:[Date]=[]letdateFormatter:DateFormatter={letformatter:DateFormatter=DateFormatter()formatter.dateStyle=.mediumreturnformatter}()lettimeFormatter:DateFormatter={letformatter:DateFormatter=DateFormatter()formatter.timeStyle=.mediumreturnformatter}()@IBActionfunctouchUpAddBtn(_sender:UIButton){dates.append(Date())// self.tableView.reloadData()self.tableView.reloadSections(IndexSet(2...2),with:UITableView.RowAnimation.automatic)}overridefuncviewDidLoad(){super.viewDidLoad()// Do any additional setup after loading the view.self.tableView.delegate=selfself.tableView.dataSource=self}funcnumberOfSections(intableView:UITableView)->Int{return3}functableView(_tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{switchsection{case0:returnkorean.countcase1:returnenglish.countcase2:returndates.countdefault:return0}}functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{ifindexPath.section<2{letcell:UITableViewCell=tableView.dequeueReusableCell(withIdentifier:self.cellIdentifier,for:indexPath)lettext:String=indexPath.section==0?korean[indexPath.row]:english[indexPath.row]cell.textLabel?.text=textreturncell}else{letcell:CustomTableViewCell=tableView.dequeueReusableCell(withIdentifier:self.customCellIdentifier,for:indexPath)as!CustomTableViewCellcell.leftLabel.text=self.dateFormatter.string(from:self.dates[indexPath.row])cell.righLable.text=self.timeFormatter.string(from:self.dates[indexPath.row])returncell}}functableView(_tableView:UITableView,titleForHeaderInSectionsection:Int)->String?{ifsection<2{returnsection==0?"한글":"영어"}returnnil}}
그리고 해당 버튼이 수행할 액션을 코드로 작성한다.
이전에 사용해봤던 dateFormatter를 사용함으로써 버튼을 누르면 날짜가 계속해서 업데이트되어 보여지는 화면을 구성해보자!
vardates:[Date]=[]letdateFormatter:DateFormatter={letformatter:DateFormatter=DateFormatter()formatter.dateStyle=.mediumformatter.timeStyle=.mediumreturnformatter}()@IBActionfunctouchUpAddBtn(_sender:UIButton){dates.append(Date())// self.tableView.reloadData() 해당 섹션의 데이터만 가져오는 것이 아니라 전체 데이터를 다시 불러오게 된다 > 비효율self.tableView.reloadSections(IndexSet(2...2),with:UITableView.RowAnimation.automatic)}
변수 dates를 만들어 빈 array를 만들어준다.
dateFormatter를 사용
해당 액션을 클릭할때마다 array에 데이터가 추가되어진다.
reloadSections를 사용하여 해당 섹션의 데이터만 불러오도록 하고
추가되는 데이터가 애니메이션 효과가 적용된 상태로 보여지도록 한다.
이전 실습에서는 단순 섹션을 추가하고 해당 섹션에 데이터를 추가해줬기 때문에 기존 코드또한 조금 변경을 해줘야 한다.
funcnumberOfSections(intableView:UITableView)->Int{return3// section의 수를 3개로 늘려주고}functableView(_tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{switchsection{case0:returnkorean.countcase1:returnenglish.countcase2:// button section에 해당하는 case를 추가해준다returndates.countdefault:return0}}functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{letcell:UITableViewCell=tableView.dequeueReusableCell(withIdentifier:self.cellIdentifier,for:indexPath)ifindexPath.section<2{// section이 2 이하일때 실행하도록 하고lettext:String=indexPath.section==0?korean[indexPath.row]:english[indexPath.row]cell.textLabel?.text=text}else{// 그 외의 case에 대해 실행하는 코드를 추가해준다cell.textLabel?.text=self.dateFormatter.string(from:self.dates[indexPath.row])}returncell}functableView(_tableView:UITableView,titleForHeaderInSectionsection:Int)->String?{ifsection<2{// 해당 코드 또한 section이 2 이하일때 실행되도록 하고returnsection==0?"한글":"영어"}returnnil// 이외에는 nil을 리턴하도록 한다}