[ TableView복습 ] 현웅's 노트앱 만들어보기

Woong·2022년 5월 7일
0
post-thumbnail

깨달은 점

-TableView의 구현과정 정리

TableView 기능으로 노트앱을 구성하는 순서를 크게 정리하면 아래 5단계와 같다.

a. 테이블뷰 생성
b. 셀 디자인 설정, 셀 identifier 설정
c. 데이터소스, 델리게이트 연결
d. 데이터소스 구현
e. 델리게이트 구현

a. 테이블뷰 생성은 TableViewcontroller를 embed in 하면 자동으로 생성하니 직접 Tableview를 그리진 않아도 된다.
b. 셀 디자인 설정, 셀 identifier 설정은 tableview controller 의 cell영역을 눌러주고 inspector 영역에서 Tableviewcell의 형태를 custom으로 할것인지.. subtitle로 할 것인지 설정해줘라! , 그리고 cell의 identifier를 cell로 지정해준다.
c. cocoatouch class 파일을 새로 생성하여 UITableViewcontroller를 생성하라. 그 클래스는 상단에는 class fileName : UITableViewcontroller{ 라고 적혀있을 것. 그 말은 즉, UITableViewcontroller는 UITableViewcontroller의 UITableViewDataSource를 채택하고 있다.
d. 생성한 UITableViewcontroller에서 numberOfRowsInSection, dequeueReusableCell 설정, 비어있는 cell에 초기 데이터를 설정해준다.- 이는 셀을 구현하는데 있어 최소의 조건이다.)
e. 델리게이트는 선택적인 요소다. 만약, 셀을 눌렀을 때 특정한 이벤트를 생성하고싶다면 델리게이트를 구현해야겠지만, 그 것이 필요하지 않다면 구현하지 않아도 된다.

1. 앱 아이콘 & 런치스크린 지정해주기

Asset에 내가 지정하고싶은 splash 이미지를 업로드해준다.

imageView 와 label 하나씩 추가하고 imageView는 Asset에 업로드해둔 splash 이미지가 저장되도록 해준다.

imageView와 label을 stackView로 묶고,
그것이 수직정렬, 중앙으로 배치 될 수 있도록 제약조건을 추가 해준다.

allignment Constraints 항목에서
horizontally in container와
vertically in container를 체크해줘서 정렬될 수 있도록 함.

런치스크린이 완성되었다!!

icon은 iphone App이라고 표기된 부분에 넣어주면 정상적으로 적용된다! 한번 실행해보자!!

2. 테이블뷰 생성

기존에 있던 Viewcontroller와 Mainstoryboard에 있는 Viewcontroller를 삭제해준다!

그리고 TableViewcontroller를 새로 하나 추가하고, Navigation Controller를 Embed in 해준다!

viewcontroller 2개를 추가해준다.
(*이건 나중에 새로운 메모 내용을 입력하는 뷰컨트롤러와 메모내용을 볼 수 있는 컨트롤러라고 생각하면 됨!)

TableViewcontroller를 더블클릭하여 내가 표시하고 싶은 메모앱이름을 써주고
NavigationViwcontroller를 눌러서 inspector 에서 prefers Large Titles를 설정해주자!

(* 실행해보면 큰 제목으로 나타나는 것을 알 수 있다.)

다음 뷰컨트롤러로 연결 할 수 있는 버튼을 만들건데
barbuttonItem을 추가하여 버튼 형태를 Add로 바꿔주자

import Foundation

class MemoContents {
    var contents : String       // 메모내용
    var date : Date             // 메모를 저장한 날짜
    
    init(contents: String) {    // 생성자를 만들어 초기값이 설정 될 수 있도록 한다.
        self.contents = contents
        date = Date()
    }
    
    static var imsiList = [     // 테이블뷰에 보여줄 임시데이터를 보관하는 배열을 만들어준다.
        MemoContents(contents: "1번메모입니다"),
        MemoContents(contents: "2번메모입니다")
    ]
}

새로운 swift파일을 만들어 새 클래스를 만들어준다.

이후, 새로운파일을 추가하여 UITableViewController를 만들어주고,
Mainstoryboard의 Scene을 선택하여 Class를 TableTableViewController라고 지정해준다.

3. 셀 design,identifier 설정

Cell의 형태를 정해줄 건데 테이블에 내용 + 날짜를 지정해 줄 것이기 때문에 Subtitle로 지정해준다.

그리고 cell의 identifier를 cell로 입력해준다.
(*이는 UITableViewController에서 활용되므로 identifier를 꼭 지정해주도록 하자.)

4. 셀 dataSource

    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
    }

    // MARK: - Table view data source

//    override func numberOfSections(in tableView: UITableView) -> Int {
//        // #warning Incomplete implementation, return the number of sections
//        return MemoContents.imsiList.count
//    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return MemoContents.imsiList.count
  1. ViewdidLoad 하단에 numberOfSections가 메서드가 보일 것임.
    이 메서드는 내가 만들고자 하는 앱에서는 사용하지 않을 것이기 때문에 주석처리해준다.
  2. 두번째, numberOfRowsInSection 메서드에는 아까 만든 MemoContents.imsiList [static] 배열의 개수만큼 반환하도록 리턴 해주자! (*셀을 몇개 출력해줄까? 묻는 메서드임)
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        // Configure the cell...
        let target = MemoContents.imsiList[indexPath.row]   // indexPath.row에 접근하여 몇번째 셀인지 알 수 있고, 해당 번째의 배열을 호출한다.
        
        cell.textLabel?.text = target.contents              // subtitle : 내용
        cell.detailTextLabel?.text = target.date.description // subtitle : 날짜
        
        return cell
  1. dequeueReusableCell 메서드에서 반환해야하는 셀을 아까 identifier로 지정해놨던 "cell"로 수정해준다.
  2. 그리고 subtitle을 구성하는 textLabel, detailTextLabel에 각각의 데이터를 지정해준다.

!!메서드에 대한 정리는 아래 링크에 해둔게 있다. 여길 보고 참조해보고 꼭 기억하자!!
Velog 웅님의 TableView 메서드 정리

그러면, 테이블뷰에서 셀을 보여지게 하는데 최소조건은 완성된것이다.

지금 cmd + R 을 해보자!
그렇다면 위와 같은 테이블뷰가 완성 됬을 것이다!

하지만, 위의 날짜+시간을 보면 기본포맷을 설정해주지 않았기에 상당히 이상하다.
Dateformatter 클래스를 호출하여 해당 데이터의 포맷을 정해주는게 낫겠다.

    let formatter : DateFormatter = {
        let format = DateFormatter()
        format.dateStyle = .short
        format.timeStyle = .short
        return format
        }()

클로저 함수를 사용해서 해당 Date의 포맷을 정해준 것이라는데 나는 솔찍히 문법적으로 잘 이해가 가질 않는다. 이 부분은 꼭 기록해두고 나중에 정리하자!

그리고 아까 dequeueReusableCell 메서드에서 정의해줬던 Subtitle(날짜)가 기억나는가? 그것을 DateFormatter로 형태를 지정해준 형태로 변환하여 정의해주자.

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        // Configure the cell...
        let target = MemoContents.imsiList[indexPath.row]   // indexPath.row에 접근하여 몇번째 셀인지 알 수 있고, 해당 번째의 배열을 호출한다.
        
        cell.textLabel?.text = target.contents              // subtitle : 내용
        cell.detailTextLabel?.text = formatter.string(from: target.date) // subtitle : 날짜
        
        return cell
    }

아까 만들어뒀던 Mainstoryboard의 Viewcontroller 두개를 segue를 활용하여 연결해준다.
그리고, 네비게이션바의 타이틀을 메모작성으로 바꿔주고 Save버튼도 추가해준다.
TextView를 제약조건 Top=0,bottom=0,leading=0, Trailing=0으로 지정하여 어떤 폰에서 사용하더라도 꽉 찬 화면이 되도록 만들어 준다.

각각의 UIViewController 파일을 추가해주고, 메인스토리보드 scene을 눌러 class를 매칭시켜주자! (뷰컨 2개)(AddViewcon,DetailViwcon)

class AddViewController: UIViewController {

    @IBOutlet weak var tvContents: UITextView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    
    @IBAction func btnSave(_ sender: UIBarButtonItem) {
        
        if tvContents.text.isEmpty {
            alert(message: "메모입력해야해요😭")
        }
        else{
            let newMemo = MemoContents(contents: tvContents.text)
            MemoContents.imsiList.append(newMemo)
            
            navigationController?.popViewController(animated: true
            )
        }
    }
    
    func alert(title : String = "경고⛔️", message: String) {
        
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let btnOk = UIAlertAction(title: "알겠어요..", style: .default, handler: nil)
        
        alert.addAction(btnOk)
        
        present(alert, animated: true, completion: nil)
    }

AddViewController에 Save를 눌렀을 때, 행동을 정의해주자.

  1. 첫번째로, TextView아울렛함수설정, Save바버튼아이템을 액션함수로 지정해준다.
  2. 만약 tvContents가 비어있을 때, alert창이 표기되도록하며, 입력되어있다면 swiftFile , 클래스 MemoCotents.imsiList (Static) 내부에 저장되도록 한다.
  3. 그리고 저장되고 난 후에 popViewController 메서드를 호출하여 이전 네비게이션컨트롤 항목으로 이동하도록 세팅해준다.

    override func viewWillAppear(_ animated: Bool) {
        tableView.reloadData()
    }

그리고 viewdidLoad 상단에 viewWillAppear 메서드를 만들어주고, 그곳에 테이블뷰데이터가 리로드되도록 만들어준다. (이렇게하고 cmd+R 해보면, 기록이 정상적으로 저장된다!)

(*테이블뷰로 화면이 넘어오면 viewWillAppear 메서드가 실행된다. viewDidLoad같은 경우는 한번 실행된다.)

이제 내버려뒀던 DetailViewController(셀 눌렀을때 행동)를 구성해보자!

tableView를 추가해주고 그 위에 tableViewCell을 추가해준다.
그리고 TableView의 Inspector에서 PrototypeCells를 2로 만들어준다.
(*이것의 목적은 위 Title에 contents를 담아줄거고, 아래에는 date를 담아줄 목적이다.)

그리고. Table View Cell (상단)의 identifier를 contentsCell이라고 지정해주고
Table View Cell (하단)의 identifier를 dateCell이라고 지정해주자
(*이것의 목적은 데이터를 각각 연결할 때, identifier이 필요하기 때문이다! 아까 cell의 identifier를 연결해줘서 dequeueReusable 메서드와 연결했던 방식과 동일하다. 아래에서도 dequeueReusable 메서드와 연결 해줄 것이기 때문에 지정하는거임.)

extension DetailViewController : UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        switch indexPath.row {
        case 0:
            let cell = tableView.dequeueReusableCell(withIdentifier: "contentsCell", for: indexPath)
            return cell
        case 1:
            let cell = tableView.dequeueReusableCell(withIdentifier: "dateCell", for: indexPath)
            return cell
        default:
            fatalError()
        }
    }
}
  1. TableView를 선택하여 오른쪽마우스로 scene - dataSource를 연결해준다.
  2. DetailViewcontroller 가장 하단부에 UITableViewDataSource를 extension해준다.
  3. 그리고 numberOfRowsInSection 메서드를 생성하고 2를 반환하도록 한다.
    (*이것은 MemoContents 클래스의 contents와 date이 나타나도록 할 것이기 때문)
  4. 아까와 마찬가지로 dequeueReusableCell 매서드를 호출하고 아까 지정해뒀던 contentsCell, dateCell을 연결해준다.
    (*TableTableViewController와 동일하게 return cell은 해줘야함!! 주의)
  5. default는 fatalError() 함수를 호출했는데 우리는 indexPath.row가 0,1 밖에 사용하지않으므로 default가 호출되는 일은 없다. 그래서 그냥 default: break 로 처리하려고했는데 그건 안된단다.. 왜일까..? 일단, 선생님이 알려준대로 적용해보긴했다.. 이유는 차후에 생각해보자..

여기서 cmd + R을 해보면 아래와같다.

데이터를 넣어주면된다.

5. data 전달


일단, segue의 이름을 지정해준다. (*나는 sgDetail 로 지정)

또, TableTableViewcontroller의 TableView를 tvListView 이라는 아울렛함수로 지정해준다.

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {     // segue를 통해 화면이 전환되기 직전에 호출되는 메서드
        if segue.identifier == "sgDetail"{
            let detailView = segue.destination as! DetailViewController
            let cell = sender as! UITableViewCell
            let indexPath = self.tvListView.indexPath(for: cell)
            
            detailView.memo = MemoContents.imsiList[indexPath!.row]
            
            }
        }
  1. segue를 통해 화면이 전환되기 직전에 호출되는 메서드인 prepare메서드를 호출한다.
  2. 조건식으로 sgDetail일 때의 코드를 작성한다..

이거... 조건식이 솔직히 너무 이해가 가지않는다.. 선생님이 한대로 코드를 타이핑해서 완성하긴했는데 도저히 이해를 못하겠어요... 질문하면 받아주세요! ㅠㅠㅠ

아무튼 여기까지가 마무리입니다. ㅠ 추후 답변을 듣거나 완성이 된다면 velog에 꼭 수정하겠다. 그래야 글이 완벽해질 것 같음.. 지금은 미완(?) 느낌.

아무튼 아래는 시연화면이다.

profile
https://github.com/iOS-Woong

0개의 댓글