세그(Segue)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


세그란?

세그는 스토리보드에서 뷰 컨트롤러 사이의 화면전환을 위해 사용하는 객체로 별도의 코드 없이도 스토리보드에서 세그를 연결하여 뷰 컨트롤러 사이의 화면전환을 구현할 수 있다.

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에서 현재 기기나 화면 상태에서 가장 적절한 화면전환 방식을 판단하여 화면을 전환)

6.두 뷰컨트롤러 사이에 세그가 생성된 것을 확인

7.시뮬레이터를 실행하여 버튼을 눌러보면 두 번째 뷰 컨트롤러의 뷰로 전환되는 것을 확인

뷰의 재사용(연습)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


뷰의 재사용 실습

이전까지의 코드를 살펴보면 dequeueReusableCell라는 걸 사용하는 것을 볼 수 있다.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if indexPath.section < 2 {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        let text: String = indexPath.section == 0 ? korean[indexPath.row] : english[indexPath.row]
        cell.textLabel?.text = text
        return cell

    } else {
        let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: self.customCellIdentifier, for: indexPath) as! CustomTableViewCell
        cell.leftLabel.text = self.dateFormatter.string(from: self.dates[indexPath.row])
        cell.righLable.text = self.timeFormatter.string(from: self.dates[indexPath.row])
        return cell
    }
}

이는 큐에 쌓여있던 재사용가능한 셀을 꺼내와 사용한다는 것을 의미한다.

위 그림을 보면 테이블뷰에는 여러 셀이 존재하고 있음을 볼 수 있다. 처음 왼쪽 그림을 보면 첫번째 셀이 맨 위에 있을 것이고 사용자가 더 아래의 셀을 보고싶다면 스크롤을 해서 화면을 올릴 것이고 그렇게 된다면 첫번째 셀은 화면에서 사라지고 왼쪽의 화면이 보이게 될 것이다. 이떄 화면에서 사라지게 되면 해당 셀은 Queue 에 가게 된다. (재사용 큐-재사용을 대기) 그러다가 밑에서 셀이 더 필요하게 되면 재사용 대기중인 셀이 그 뒤에 붙게된다. 즉, 화면 바깥으로 나간 셀은 다시 재사용이 필요해서 아래로 붙게 되기에 우리가 위에서 사용했던 dequeueReusableCell 는 이를 의미한다. (큐에 들어갔다가 다시 큐에서 나오는 것)

이를 확인해보기 위해 간단하게 코드를 작성해봅시다.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if indexPath.section < 2 {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        let text: String = indexPath.section == 0 ? korean[indexPath.row] : english[indexPath.row]
        cell.textLabel?.text = text

        if indexPath.row == 1 {
            cell.backgroundColor = UIColor.red
        } else {  // 해당 코드가 없으면 재사용되는 코드 모두가 빨간색이 되어버린다
            cell.backgroundColor = UIColor.white  
        }
        return cell

    } else {
        let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: self.customCellIdentifier, for: indexPath) as! CustomTableViewCell
        cell.leftLabel.text = self.dateFormatter.string(from: self.dates[indexPath.row])
        cell.righLable.text = self.timeFormatter.string(from: self.dates[indexPath.row])
        return cell
    }
}

그런데 우리가 셀을 재사용하지 않는다면 어떻게 될까? > 코드를 작성해보자.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if indexPath.section < 2 {
        let cell: UITableViewCell = UITableViewCell()  // 셀을 하나 생성
        let text: String = indexPath.section == 0 ? korean[indexPath.row] : english[indexPath.row]
        cell.textLabel?.text = text

        if indexPath.row == 1 {
            cell.backgroundColor = UIColor.red
        } else {
            cell.backgroundColor = UIColor.white
        }
        return cell

    } else {
        let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: self.customCellIdentifier, for: indexPath) as! CustomTableViewCell
        cell.leftLabel.text = self.dateFormatter.string(from: self.dates[indexPath.row])
        cell.righLable.text = self.timeFormatter.string(from: self.dates[indexPath.row])
        return cell
    }
}

이렇게 되면 테이블 뷰의 로우가 정말 많은 숫자가 된다면 셀을 그에 맞게 만들어줘야 하기에 메모리의 낭비가 굉장히 심해질 것이다. 즉 화면에서 벗어난 셀은 큐에 담아두고 쓰고 하는 방식을 채택하는 것이 옳다.

뷰의 재사용

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


뷰의 재사용

iOS 기기는 한정된 메모리를 가지고 애플리케이션을 구동한다.
만약 사용자에게 보여주고 싶은 데이터가 매우 많고, 데이터의 양만큼 많은 뷰가 필요하다면 어떻게해야할까?

화면에 표시할 수 있는 뷰의 개수는 한정되어 있지만, 표현해야 하는 데이터가 많은 경우 반복된 뷰를 생성하기보다는 뷰를 재사용할 수 있다. 사용할 수 있는 메모리가 작아서 데이터의 양만큼 많은 뷰를 생성하는 것은 메모리를 많이 낭비할 수밖에 없기 때문이기에 뷰를 재사용함으로써 메모리를 절약하고 성능 또한 향상할 수 있다.

재사용의 대표적인 예

iOS 환경에서 뷰를 재사용하는 대표적인 예로 UITableViewCell, UICollectionViewCell 등이 있다.

  • UITableViewCell : UITableView의 셀
  • UICollectionViewCell : UICollectionView의 셀

재사용 원리

  1. 테이블뷰 및 컬렉션뷰에서 셀을 표시하기 위해 데이터 소스에 뷰(셀) 인스턴스를 요청
  2. 데이터 소스는 요청마다 새로운 셀을 만드는 대신 재사용 큐 (Reuse Queue)에 재사용을 위해 대기하고있는 셀이 있는지 확인 후 있으면 그 셀에 새로운 데이터를 설정하고, 없으면 새로운 셀을 생성
  3. 테이블뷰 및 컬렉션뷰는 데이터 소스가 셀을 반환하면 화면에 표시
  4. 사용자가 스크롤을 하게 되면 일부 셀들이 화면 밖으로 사라지면서 다시 재사용 큐에 들어감
  5. 위의 1번부터 4번까지의 과정이 계속 반복

In Apple Docs…

UITableViewDelegate

참고한 애플 문서

Declaration

class UICollectionView : UIScrollView

테이블 뷰 셀 커스터마이징(연습)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


테이블 뷰 셀 커스터마이징

이제 데이터를 동적으로도 받아오는 방법을 익혀보았으니, 셀을 커스터마이징을 해보도록 하자!

main.storyboard 로 가봅시다.

  1. tableViewCell 을 하나 추가해준다.
  2. style: Custom 으로 설정해준다.
  3. Identifier: customCell 로 지정해준다.
  1. 해당 셀 위에 레이블 두개를 추가해준다.
  2. 셀의 인스턴스 클래스를 만들기 위해 코코아터치 클래스를 생성해준다.

각각의 레이블이 행할 outlet을 만들어준다.

import UIKit

class CustomTableViewCell: UITableViewCell {

    @IBOutlet var leftLabel: UILabel!
    @IBOutlet var righLable: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

그리고 다시 main.storyboard로 이동합니다. > 각각의 커스텀셀의 인터페이스를 완성해봅시다.

이를 실제로 테이블뷰에서 사용하기 위해서는 viewController로 이동!

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    let cellIdentifier: String = "cell"
    let customCellIdentifier: String = "customCell"

    let korean: [String] = ["가", "나", "다", "라"]
    let english: [String] = ["a", "b", "c", "d"]

    var dates: [Date] = []

    let dateFormatter: DateFormatter = {
        let formatter: DateFormatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter
    }()

    let timeFormatter: DateFormatter = {
        let formatter: DateFormatter = DateFormatter()
        formatter.timeStyle = .medium
        return formatter
    }()

    @IBAction func touchUpAddBtn(_ sender: UIButton) {
        dates.append(Date())
//        self.tableView.reloadData()
        self.tableView.reloadSections(IndexSet(2...2), with: UITableView.RowAnimation.automatic)
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.tableView.delegate = self
        self.tableView.dataSource = self
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section {
        case 0:
            return korean.count
        case 1:
            return english.count
        case 2:
            return dates.count
        default:
            return 0
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if indexPath.section < 2 {
            let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
            let text: String = indexPath.section == 0 ? korean[indexPath.row] : english[indexPath.row]
            cell.textLabel?.text = text
            return cell

        } else {
            let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: self.customCellIdentifier, for: indexPath) as! CustomTableViewCell
            cell.leftLabel.text = self.dateFormatter.string(from: self.dates[indexPath.row])
            cell.righLable.text = self.timeFormatter.string(from: self.dates[indexPath.row])
            return cell
        }
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section < 2 {
            return section == 0 ? "한글" : "영어"
        }
        return nil
    }
}

테이블 뷰에 동적으로 데이터 추가해보기(연습)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


테이블 뷰에 동적으로 데이터 추가해보기

실시간으로 추가되는 데이터가 테이블뷰에 반영되는 것을 만들어보자!

tableView위에 button 하나를 만들어봅니다.

그리고 해당 버튼이 수행할 액션을 코드로 작성한다.
이전에 사용해봤던 dateFormatter를 사용함으로써 버튼을 누르면 날짜가 계속해서 업데이트되어 보여지는 화면을 구성해보자!

var dates: [Date] = []

let dateFormatter: DateFormatter = {
    let formatter: DateFormatter = DateFormatter()
    formatter.dateStyle = .medium
    formatter.timeStyle = .medium
    return formatter
}()

@IBAction func touchUpAddBtn(_ sender: UIButton) {
    dates.append(Date())
//        self.tableView.reloadData()  해당 섹션의 데이터만 가져오는 것이 아니라 전체 데이터를 다시 불러오게 된다 > 비효율
    self.tableView.reloadSections(IndexSet(2...2), with: UITableView.RowAnimation.automatic)
}
  1. 변수 dates를 만들어 빈 array를 만들어준다.
  2. dateFormatter를 사용
  3. 해당 액션을 클릭할때마다 array에 데이터가 추가되어진다.
  4. reloadSections를 사용하여 해당 섹션의 데이터만 불러오도록 하고
  5. 추가되는 데이터가 애니메이션 효과가 적용된 상태로 보여지도록 한다.

이전 실습에서는 단순 섹션을 추가하고 해당 섹션에 데이터를 추가해줬기 때문에 기존 코드또한 조금 변경을 해줘야 한다.

func numberOfSections(in tableView: UITableView) -> Int {
    return 3  // section의 수를 3개로 늘려주고
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
    case 0:
        return korean.count
    case 1:
        return english.count
    case 2:  // button section에 해당하는 case를 추가해준다
        return dates.count
    default:
        return 0
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
    if indexPath.section < 2 {  // section이 2 이하일때 실행하도록 하고
        let text: 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])
    }

    return cell
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section < 2 {  // 해당 코드 또한 section이 2 이하일때 실행되도록 하고
        return section == 0 ? "한글" : "영어"
    }
    return nil  // 이외에는 nil을 리턴하도록 한다
}

작성한 코드를 실행해보면 아래와 같이 보여질 것이다!