본문 바로가기

카테고리 없음

96 iOS TableView

** UITableView

=> UIScrollView를 상속받은 뷰로 행(셀) 단위로 분리해서 데이터를 표현하는 뷰

=> Section(Group) 단위로 분할이 가능하지만 열단위 분할은 1개만 생성 가능

=> UITableVieDataSource 프로토콜에 필수 메소드인 행의 개수를 설정하는 메소드와 셀을 만들어서 리턴하는 메소드 2개를 이용해서 구현한다.

안드로이드의 ListView는 Adapter에 이미 메소드가 구현되어 잇어서 Adapter를 이용해서 출력을 하지만 iOS는 직접 구현을 해야 한다.

=> 사용자 정의 셀을 만들 때는 UITableViewCell이라는 클래스를 상속받는 클래스를 만들고 디자인을 하고 필요한 변수를 생성한 후 셀을 만들어주는 메소드에서 기본 셀을 만드는 코드를 호출하고 강제 형변환을 수행하면 된다.

 

=> 테이블 뷰를 출력하는 방법

UITableView를 UIViewController위에 배치하는 방법 : 크기변경이 가능하다는 장점은 있지만 디바이스 크기에 반응을 하도록 만들어야 한다. Delegate와 DataSource를 설정해서 메소드를 구현해야 한다.

 

UITableViewController를 배치 : 무조건 화면에 가득차게 되고 크기 조절이 되지 않지만 디바이스 크기에 반응을 할 필요가 없어지고 메소드가 이미 구현되어 있기 때문에 필요한 메소드를 오버라이딩 하면 된다.

 

1. 프로젝트 기본 구조 : 네비게이션 컨트롤러를 배치하고 테이블 뷰 컨트롤러를 배치해서 테이블 뷰의 항목을 클릭하면 세부 항목을 출력하도록 생성

1) 기본 프로젝트 생성 - 아이패드 용으로는 MasterDetail 구조를 만들어도 된다.

 

2) UITableViewController를 상속받는 클래스를 생성 - RootViewController

 

3) Main.storyboard 파일을 열어서 기본 제공되는 뷰컨트롤러를 제거

 

4) Main.storyboard 파일에 TableViewController를 추가하고 Class 속성과 Storyboard ID 속성을 변경 - RootViewController

 

5) 추가한 뷰 컨트롤러를 시작 뷰 컨트롤러로 설정

=> 뷰 컨트롤러의 attribute inspector에서 is Initial View Controller 를 체크

 

6) Navigation 컨트롤러를 추가

=> 뷰 컨트롤러를 선택하고 [Editor] - [Embed In] - [Navigation Controller] 메뉴를 실행

 

7) RootViewController.swift 파일에 테이블 뷰에 출력할 데이터를 소유한 배열 프로퍼티를 생성

 

    //테이블에 출력할 데이터 배열
    var titles = Array<String>()
    var contents = Array<String>()

 

8) RootViewController.swift 파일의 viewDidLoad 메소드에 배열의 데이터를 초기화하는 코드를 작성

 

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "iOS 실습 내용"
        
        titles.append("사용자 정의 셀")
        contents.append("셀을 원하는 모양으로 만들어서 출력")
    }

 

 

9) 섹션의 개수를 설정하는 메소드를 삭제하거나 수정

    //섹션의 개수를 설정하는 메소드
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

 

10) 행의 개수를 설정하는 메소드 수정

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return titles.count
    }

 

11) 셀의 모양을 만드는 메소드를 생성 - 주석 처리되어 있다.

 

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //셀의 구분자를 생성
        let cellIdentifier = "Cell"
        //테이블 뷰에서 재사용 가능한 셀을 받아온다.
        var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
        //재사용 가능 셀이 없으면 생성, style을 subtitle로 설정하면 레이블2개, 이미지뷰 1개, 엑세서리뷰 1개 모두 사용 가능
        if cell == nil{
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
        }
        //레이블에 텍스트를 출력
        cell!.textLabel!.text = titles[indexPath.row]
        cell!.detailTextLabel!.text = contents[indexPath.row]
        //엑세서리 모양을 설정
        cell!.accessoryType = .disclosureIndicator

        return cell!
    }

 

 

12) 셀을 눌렀을 때 호출되는 메소드를 재정의

 

    //셀을 선택했을 때 호출되는 메소드
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        code
    }

 

 

2. 사용자 정의 셀 사용하기

1) UITableViewCell 을 상속받는 클래스를 생성

 

2) 스토리보드에서 TableView에 TableViewCell을 추가 - 있으면 추가할 필요가 없음

 

3) 스토리보드에서 추가한 셀의 Class와 Identifier를 설정

 

4) 스토리보드에 필요한 디자인 작업을 한다.

 

5) 셀에 디자인 한 것 중에서 수정을 하거나 이벤트 처리를 해야 하는 것들이 있으면 Cell 클래스에 작업

 

6) 뷰 컨트롤러에서 셀을 만드는 메소드를 수정

=> 셀의 Identifier를 확인하고 셀을 만든 후 자신의 셀클래스로 강제 형변환을 한다.

셀에 필요한 작업을 수행

 

3. 사용자 정의 셀을 만들어서 이미지 뷰와 스위치를 배치하고 스위치를 누르면 이미지 뷰의 이미지가 변경이 되는 실습

 

1) 프로젝트에 필요한 이미지르 ㄹ복사

 

2) 작업을 수ㅐㅎㅇ할 테이블 뷰 컨트롤러를 상속받는 클래스를 생성

=> CustomCellViewController

 

3) Main.storyboard파일에 TableViewController를 추가하고 Class와 Storyboard ID를 설정

 

4) 프로젝트 구조를 알아보기 쉽도록 하기 위해서 RootViewController를 선택하고 control키를 누른 채 추가한 뷰컨트롤러로 드래그해서 push를 선택해준다.

 

5) RootViewController.swift 파일 의 셀을 선택했을 때 호출되는 메소드를 수정

    //셀을 선택했을 때 호출되는 메소드
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.row == 0{
            let customCellViewController = self.storyboard?.instantiateViewController(withIdentifier: "CustomCellViewController") as! CustomCellViewController
            self.navigationController?.pushViewController(customCellViewController, animated: true)
        }
    }

 

 

6) UITableViewCell 로 부터 상속받는 클래스를 생성 - MyCustomCell

 

7) Main.storyboard파일을 열어서 CustomCellViewController 의 TableView에 셀이 있는지 확인하고 없으면 추가

 

8) 셀의 클래스를 자신이 만든 클래스로 변경하고 identifier로 변경

 

9) 셀의 이미지뷰와 스위치를 배치

 

10) MyCustomCell 클래스에 변수와 메소드를 생성

=> 이미지 뷰에 imgView라는 변수를 연결

=> 스위치의 valueChanged 이벤트에 메소드를 연결

    @IBOutlet weak var imgView: UIImageView!
    @IBAction func imageSwitch(_ sender: Any) {
    }

 

* 서로 다른 클래스에서의 데이터 공유
1. 전체가 공유할 수  있는 변수를 만들어서 사용 - 전역변수를 생성 , 가장 비추천하는 방법
=> java에서는 public 클래스를 만들고 static 변수를 public으로 생성
=> swift에서는 class 외부에 변수를 만들면 전역변수가 된다.
=> 싱글톤 패턴의 클래스를 디자인하고 그 안에 public 변수를 생성
=> 각 SDK의 구조를 이해해서 생성
Java WebApplication : application 객체 이용
iOS : AppDelegate 클래스의 객체가 entry point가 되고 애플리케이션의 모든 곳에서 접근이 가능. AppDelegate 클래스에 데이터를 만들면 애플리케이션 모든 곳에서 데이터 사용이 가능하다.

iOS 애플리케이션 구조
AppDelegate -> SceneDelegate(iOS 13이상에서만 생성) -> 각각의 뷰 컨트롤러

2. 2개의 클래스 관계를 확인햇는데 하나의 클래스에서 다른 클래스의 객체를 생성하는 경우
=> 안에서 객체가 만들어지는 클래스에 변수를 생성하고 포함하고 있는 클래스의 코드에서 객체를 생성할 때 데이터를 넘겨주면 된다.
iOS에서 많이 사용하는 방식이다.

3. 다른 클래스의 객체를 사용하기는 하지만 직접 생성하지 않는 경우
=> 호출하는 메소드의 리턴 값을 이용 : Service에서 Dao가 만든 데이터 사용
=> 메소드를 만들 때 매개변수를 참조형으로 만들어서 그 안에 데이터를 넣어주는 방식 : Controller에서 Service가 만든 결과 사용
=> Android의 경우는 직접 객체 생성도 하지 않고 메소드도 호출하지 않기 때문에 Intent에 데이터를 저장해서 전달하거나 Broadcast를 만들어서 데이터를 전달

 

 

11) AddDelegate.swift 파일에 모든 곳에서 사용할 배열을 2개 생성

    //이미지 파일 이름 배열
    //CustomCellController에서 출력할 이미지 파일 이름
    var images1 = ["image1.png", "image2.png", "image3.png"]
    var images2 = ["image4.png", "image5.png", "image6.png"]

 

12) CustonCellViewController.swift 파일의 데이블 뷰 출력 메소드를 수정하거나 생성해서 image1의 데이터를 출력

=> 섹션의 개수를 설정하는 메소드는 제거해도 되고 1을 리턴하도록 해주어도 된다.

 

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        //AppDelegate 클래스의 인스턴스에 대한 참조를 생성
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        
        return appDelegate.images1.count
    }

 

 

=> 셀을 생성해주는 메소드 생성

    //셀을 생성해주는 메소드
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        //셀을 수정한 적이 없으면 아무 이름이나 상관없고
        //셀을 수정했을면 셀의 Identifier를 설정해야 합니다.
        //이 부분을 틀리면 Identifier 예외가 발생
        //인터페이스 빌더에서 셀을 선택하고 ID를 설정하던가 이 코드를 수정
        let cellIdentifier = "MyCustomCell"
        
        //자신의 셀 클래스로 형 변환
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? MyCustomCell
        
        //데이터 출력
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        
        cell?.imgView.image = UIImage(named: appDelegate.images1[indexPath.row])
        

        return cell!
    }

 

=> 셀의 높이를 설정하는 메소드

    //셀의 높이를 설정하는 메소드
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 70
    }

 

 

13) MyCustomCell.swift 파일에 스위치가 값을 변경할 때 호출될 메소드의 코드를 작성

 

    @IBAction func imageSwitch(_ sender: Any) {
        //선택된 행 번호를 찾아오기
        //사용자 정의 셀에 이벤트 핸들링을 위한 뷰가 포함되면 이 코드를 많이 사용
        let tableView = self.superview! as! UITableView
        let indexPath = tableView.indexPath(for: self)
        
        //스위치의 상태값 가져오기
        let imageSwitch = sender as! UISwitch
        
        //공유 데이터를 사용하기 위한 참조 변수 만들기
        let delegate = UIApplication.shared.delegate as! AppDelegate
        //스위치의 상태에 따라 이미지를 다르게 적용
        if imageSwitch.isOn == true{
            imgView.image = UIImage(named: delegate.images1[indexPath!.row])
        }else{
            imgView.image = UIImage(named: delegate.images2[indexPath!.row])
        }
    }

 

* GUI 프로그램에서의 뷰의 이벤트 처리
=> MS - Windows(Win API, MFC, VC#), Android, iOS, JavaScript 등에서 이벤트 처리 메소드는 기본적으로 하나 이상의 매개변수를 소유한다.
첫번째 매개변수를 이용하면 이벤트가 발생한객체를 알 수 있다.
MS - Windows 나 iOS는 첫번째 매개변수가 이벤트가 발생한 객체이며 이름은 sender
=> 안드로이드는 id를 찾아와서 객체를 다시 찾음
=> 자바스크립트는 this

 

14) 네비게이션 바에는 타이틀이 출력되는 것이 좋다.

=> CustomCellViewController.swift 파일의 viewDidLoad

 

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "사용자 정의 셀"

    }

 

 

15) 애플리케이션을 완성

=> 각 파일의 코드를 확인

=> 여러개의 메소드에서 동일한 코드가 존재하면 별도의 메소드로 생성하던가 초기화 메소드에서 수행하는 것이 좋다.

=> 하나의 메소드코드가 너무 길면 메소드의 내용을 분할해서 구현하는 것이 좋다.

하나의 메소드 길이는 20줄을 넘지 않는 것을 권장

 

 

** 테이블 뷰의 데이터 편집

1. 테이블 뷰의 데이터를 다시 출력

1) reloadData 라는 메소드를 호출

=> 테이블 뷰를 출력해주는 메소드를 다시 호출해서 테이블 뷰를 출력한다.

 

2) 삽입과 삭제 애니매이션 이용 가능

 

테이블 뷰.beginUpdates()

테이블 뷰.insertRows(at:[indexPath], with:애니메이션 종류

테이블 뷰.endUpdates()

 

=> 삭제는 insertRows 대신에 deleteRows를 호출하면 된다.

 

2. 삭제나 이동은 제공되는 기능을 이용할 수 있다.

=> 삭제나 이동은 테이블 뷰 컨트롤러의 메소드가 존재하고 실제 데이터의 삭제나 이동만 처리해주면 된다.

=> 삭제나 이동의 UI는 직접 핸들링 할 필요가 없다.

 

=> 삭제나 이동을 하고자 할 때는 editButton을 추가

editButton을 누르면 데이터의 왼쪽에 삭제 버튼이 생기고 오른쪽에는 이동할 수 있도록 선이 보인다.

 

1) 삭제의 경우는 아래 메소드를 재정의 한다.

func tableView(_ tableView:UITableView, commit

editingStyle:UITableViewCell.EditingStyle forRowAt indexPath:IndexPath) 메소드에서 실제 삭제 작업을 하면 된다.

 

2) 이동의 경우는 아래 메소드를 재정의

func tableView(_ tableView:UITableView, moveRowAt sourceIndexPath:IndexPath, todestination indexPath)

이 메소드에서 moveRowAt 이 선택한 인덱스이고 to가 드랍한 인덱스이다.

배열의 데이터에서 moveRowAt 과 to의 데이터를 교채해 주어야 한다.

배열에서 위치를 바꾸고자 할 때는 변경하고자 하는 데이터를 다른 곳에 복사하고 원래 위치에서 삭제한 후 변경될 위치에 삽입을 해야 한다.

 

3. 테이블 뷰의 데이터 삽입과 삭제 그리고 위치 이동

1) 새로운 화면으로 사용한 UITableViewController로 부터 상속받는 클래스를 생성

=> EditTable

 

2) Main.storyboard 파일에 TableViewController를 배치하고 Class 속성과 Stotyboard ID 속성을 설정

 

3) RootViewController를 선택하고 control키를 누른 채 추가한 컨트롤러에 떨어뜨리고 push를 선택

 

4) RootViewController.swift 파일의 viewDidLoad 메소드에서 배열에 데이터를 추가

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "iOS 실습 내용"
        
        titles.append("사용자 정의 셀")
        contents.append("셀을 원하는 모양으로 만들어서 출력")
        
        titles.append("테이블 뷰 편집")
        contents.append("데이터를 삽입하고 삭제 그리고 이동")
        
    }

 

5) RootViewController.swift 파일의 셀을 선택햇을 때 호출되는 메소드에 추가

        }else if indexPath.row == 1{
            let editTableViewController = self.storyboard?.instantiateViewController(withIdentifier: "EditTableViewController") as! EditTableViewController
            self.navigationController?.pushViewController(editTableViewController, animated: true)
        }

 

6) EditTableViewController.swift 파일에 테이블 뷰에 출력할 데이터 배열을 인스턴스 변수로 선언

 var apple = Array<String>()

 

7) EditTableViewController.swift 파일의 viewDidLoad 메소드에 데이터를 초기화하는 코드를 작성

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "테이블 뷰 편집"
        
        apple.append("iPod")
        apple.append("MacBook Pro")
        apple.append("iPhone")
        apple.append("iPad")
    }

 

8) EditTableViewController.swift 파일의 데이블 뷰 출력 관련 메소드들을 편집

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return apple.count
    }

 

=> 셀을 만들어주는 메소드를 생성

 

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //셀의 구분자를 생성
        let cellIdentifier = "cell"
        //테이블 뷰에서 재사용 가능한 셀을 받아옵니다.
        var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
        //재사용 가능 셀이 없으면 생성
        //style을 subtitle로 설정하면 레이블 2개, 이미지 뷰 1개
        //엑세서리 뷰 1개 모두 사용 가능
        if cell == nil{
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
        }
        //레이블에 텍스트를 출력
        cell!.textLabel!.text = titles[indexPath.row]
        cell!.detailTextLabel!.text = contents[indexPath.row]
        //엑세서리 모양을 설정
        cell!.accessoryType = .disclosureIndicator

        return cell!
    }
    

 

9) 네비게이션 바의 오른쪽에 삽입 버튼을 추가해서 버튼을 누르면 대화상자를 출력해서 한 줄의 문자열을 입력하도록 하고 이 문자열을 데이터에 추가하고 추가한 내역을 데이블 뷰에 반영

=> EditTableViewController.swift 파일에 데이터 추가를 위한 메소드를 생성

    //네비게이션 바의 바 버튼 아이템을 클랙했을 때 호출될 메소드
    @objc func insertItem(_ sender:UIBarButtonItem){
        //한 줄 입력을 위한 대화상자를 생성
        let alert = UIAlertController(title: "아이템 등록", message: "등록할 아이템을 입력하세요", preferredStyle: .alert)
        //한 줄 입력을 위한 텍스트 필드를 추가
        alert.addTextField(configurationHandler: {(tf) -> Void in tf.placeholder = "등록할 아이템 이름을 입력하세요!!"
        })
        //버튼생성
        alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
        //화면 출력
        present(alert, animated: true)

 

 

=> viewDidLoad 메소드의 오른쪽에 추가 바 버튼을 추가하고 앞에서 만든 메소드를 호출하도록 설정하는 코드를 작성

 

        alert.addAction(UIAlertAction(title: "확인", style: .default, handler: {(_) Void in
            //텍스트 필드에 입력한 내용을 가져오기
            let item = alert.textField?[0].text
            //데이터에 추가
            //핸들러(클로저)안에서 클래스에 만든 인스턴스 변수 호출할 때 반드시 self를 붙여야 한다.
            self.apple.append(item!)
            //테이블 뷰 재출력
            self.tableView.reloadData()
        }))
        //화면 출력
        present(alert, animated: true)

 

 

=>viewDidLoad 메소드의 오른쪽에 추가 바 버튼을 추가하고 앞에서 만든 메소드를 호출하도록 설정하는 코드를 작성

     

        //add 아이콘을 이용하고 누르면 self에 있는 insertItem이라는
        //메소드를 호출하는 바 버튼 아이템 생성
        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertItem(_:)))
        
        //오른쪽에 바 버튼 아이템 배치
        self.navigationItem.rightBarButtonItems = [editButtonItem, addButton]

 

 

=>데이터를 삽입하는 메소드에서 reloadData 대신에 애니메이션을 이용하는 방식으로 코드를 변경

   

    //네비게이션 바의 바 버튼 아이템을 클릭했을 때 호출될 메소드
    @objc func insertItem(_ sender:UIBarButtonItem){
        //한 줄 입력을 위한 대화상자를 생성
        let alert = UIAlertController(title: "아이템 등록", message: "등록할 아이템을 입력하세요", preferredStyle: .alert)
        //한 줄 입력을 위한 텍스트 필드를 추가
        alert.addTextField(configurationHandler: {(tf) -> Void in
            tf.placeholder = "등록할 아이템 이름을 입력하세요!!!"
        })
        //버튼을 생성
        alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "확인", style: .default, handler: {(_) -> Void in
            //텍스트 필드에 입력한 내용을 가져오기
            let item = alert.textFields?[0].text
            //데이터에 추가
            //핸들러(클로저)안에서 클래스에 만든 인스턴스 변수 호출할 때
            //반드시 self를 붙여야 합니다.
            
            //self.apple.append(item!) //가장 나중에 추가
            //다음 페이지의 데이터를 가져올 때는 나중에 추가
            
            self.apple.insert(item!, at: 0) //앞에 추가
            //새로운 데이터를 가져오는 업데이트를 할 때는 앞에 추가
            
            //테이블 뷰 재출력
            //self.tableView.reloadData()
            
            //테이블 뷰 애니메이션 추가
            self.tableView.beginUpdates()
            self.tableView.insertRows(at: [indexPath(row: 0, section: 0)], with: .right)
            
            //append로 추가한 경우
            /*
            self.tableView.insertRows(at: [indexPath(row: self.apple.count-1, section: 0)], with: .right)
            */
            self.tableView.endUpdates()
        }))
        //화면 출력
        present(alert, animated: true)
    }

 

 

=> edit 버튼을 눌렀을 때 보여지는 아이콘을 설정 메소드를 생성

    //edit 버튼을 누르면 보여지는 아이콘의 모양을 설정
    override func tableView(_ tableView:UITableView, editingStyleForRowAt indexPath:IndexPath) -> UITableViewCell.EditingStyle{
        return .delete
    }
    //실제 삭제 버튼을 눌렀을 때 호출되는 메소드
    override func tableView(_ tableView:UITableView, commit EditingStyle:UITableViewCell.EditingStyle, forRowAt indexPath:IndexPath){
        //실제 데이터를 삭제하고 삭제한 후의 행동을 설정
        self.apple.remove(at: indexPath.row)
        self.tableView.beginUpdates()
        self.tableView.deleteRows(at: [IndexPath(row: indexPath.row, section:0)], with: .bottom)
        self.tableView.endUpdates()
    }

 

 

11) 셀의 위치 변경

=> edit 버튼을 눌렀을 때 호출되는 메소드를 수정해서 이동할 수 있는 아이콘이 보이도록 설정

 //edit 버튼을 누르면 보여지는 아이콘의 모양을 설정
    override func tableView(_ tableView:UITableView, editingStyleForRowAt indexPath:IndexPath) -> UITableViewCell.EditingStyle{
        return .none
    }

 

=> 셀의 이동이 끝나면 호출되는 메소드를 

    //셀의 이동이 끝났을 때 호출되는 메소드
    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        //처음 잡은 자리의 데이터를 다른 곳으로 복사
        let moveObj = self.apple[sourceIndexPath.row]
        //배열에서 처음 잡은 자리의 데이터를 삭제
        self.apple.remove(at: sourceIndexPath.row)
        //배열에 놓은 자리에 추가
        self.apple.insert(moveObj, at: destinationIndexPath.row)
    }