프로젝트C. Weather

  • UIKit
    • UITableView
    • UITableViewCell
  • View Reuse
    • Table View Cell Customize
  • Swift
    • Codable
    • JSONDecoder

키워드: 테이블뷰, JSON, Delegation 패턴, 내비게이션 인터페이스

테이블뷰

테이블뷰 만들기

메인스토리보드에 테이블뷰컨트롤러 아니고 테이블뷰 추가하기
테이블뷰셀 추가하고, reuse identifier: cell 설정하기

dataSource와 delegate를 View Controller로 설정하기

  • 인터페이스 빌더로
    메인스토리보드 View Controller Scene에서 테이블뷰 오른쪽마우스, 노란색네모 뷰컨트롤러로 연결해주기
  • 코드로
self.tableView.delegate = self
self.tableView.dataSource = self

style: grouped로 변경

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    let cellIdentifier: String = "cell"
    
    let korean: [String] = ["가", "나", "다", "라", "마", "바", "사", "아", "자", "차", "카", "타", "파", "하"]
    let english: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    //한글과 영어 두개의 섹션
    func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }
    
    //섹션에 있는 행의 개수
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section {
        case 0:
            return korean.count
        case 1:
            return english.count
        default:
            return 0
        }
    }
    
    //행에 해당하는 셀 리턴
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        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
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return section == 0 ? "한글" : "영어"
    }
}

동적으로 데이터 추가하기

셀 아래 버튼을 추가하고 섹션을 추가한 뒤
버튼을 누를 때마다 날짜를 그 섹션에 추가하기

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    let cellIdentifier: String = "cell"
    
    let korean: [String] = ["가", "나", "다", "라", "마", "바", "사", "아", "자", "차", "카", "타", "파", "하"]
    let english: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
    var dates: [Date] = []

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

    @IBOutlet weak var touchUpAddButton: UIButton!
    
    @IBAction func touchUpAddButton(_ 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.
    }

    //한글과 영어 두개의 섹션
    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 {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        
        // 셀 생성
        if indexPath.section < 2 {
            let text: String = indexPath.section == 0 ? korean[indexPath.row] : english[indexPath.row]
            cell.textLabel?.text = text
        } else {
            cell.textLabel?.text = self.dateFormatter.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 Cell추가
그 안에 label 두 개 추가
Style: custom
reuse identifier: customCell

코코아터치클래스 UITableViewCell생성: customCell의 인스턴스를 만들기 위해서
customCell의 클래스를 방금 생성한 Class와 연결: CustomTableViewCell

//CustomTableViewCell.swift

@IBOutlet var leftLabel: UILabel!
@IBOutlet var rightLabel: UILabel!
//ViewController.swift

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
    }()
    
 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 {
            // 커스텀테이블뷰셀이라고 지정하고 일반테이블뷰의 메서드를 사용했기 때문에 as! 필요
            // 강제 타입캐스팅 말고 좋은 방법 찾아보기
            let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: self.customcellIdentifier, for: indexPath) as! CustomTableViewCell
            cell.leftLabel?.text = self.dateFormatter.string(from: self.dates[indexPath.row])
            cell.rightLabel?.text = self.timeFormatter.string(from: self.dates[indexPath.row])
            
            return cell
        }
        
    }   

뷰의 재사용

UITableViewCell, UICollectionViewCell

모바일에서는 PC보다 자원이 한정되어 있어 메모리를 절약하기 위해
화면에서 안보이면 재사용큐에 들어간 후 재사용되어 나온다.

let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)

register(:forCellReuseIdentifer:)이나 register(:forCellReuseIdentifier:)으로 Identifier를 반드시 설정해줘야 한다. (코드로 설정하는 방법)

segue

show, show detail

show detail은 split view에서 사용한다
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html

다음 화면으로 데이터 전달하기

embed in > Navigation Controller
뷰 컨트롤러 추가
그 안에 레이블 추가
셀에서 뷰 컨트롤러 드래그하고 Selection Segue > show 선택
코코아터치클래스 생성 UIViewController

//ViewController.swift
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        //segue.identifier =
        
        guard let nextViewController: SecondViewController = segue.destination as? SecondViewController else {
            return
        }
        
        guard let cell: UITableViewCell = sender as? UITableViewCell else {
            return
        }
        
        // 세컨드뷰컨트롤러에서 텍스트레이블이 아직 생성되어 있지 않기 때문에 textToSet에 값을 저장해서 넘겨준다
        nextViewController.textToSet = cell.textLabel?.text
    }

//SecondViewController.swift
import UIKit

class SecondViewController: UIViewController {

    var textToSet: String?
    
    @IBOutlet weak var textLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        self.textLabel.text = self.textToSet
    }

}

JSON

Codable = Decodable & Encodable

메인스토리보드에서 테이블뷰컨트롤러와 셀을 생성하고
셀의 Style: Subtitle, identifier: cell 설정한다

dataSource와 tableView를 각각 아웃렛과 뷰컨트롤러를 연결해준다

//ViewController.swift

import UIKit

class ViewController: UIViewController, UITableViewDataSource {
    
    
    @IBOutlet weak var tableView: UITableView!
    let cellIdentifier: String = "cell"
    var friends: [Friends] = []
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.friends.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        
        let friend: Friends = self.friends[indexPath.row]
        
        cell.textLabel?.text = friend.nameAndAge
        cell.detailTextLabel?.text = friend.fullAddress
        return cell
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonDecoder: JSONDecoder = JSONDecoder()
        guard let dataAsset: NSDataAsset = NSDataAsset(name: "friends") else {
            return
        }
        
        do {
            //디코드타입은 프렌즈배열
            self.friends = try jsonDecoder.decode([Friends].self, from: dataAsset.data)
        } catch {
            print(error.localizedDescription)
        }
        
        self.tableView.reloadData()
        
    }


}
//Friends.swift
//{
//    "name":"하나",
//    "age":22,
//    "address_info": {
//        "country":"대한민국",
//        "city":"울산"
//    }
//}

import Foundation

struct Friends: Codable {
    struct Address: Codable {
        let country: String
        let city: String
    }
    
    let name: String
    let age: Int
    let addressInfo: Address
    
    var nameAndAge: String {
        return self.name + "(\(self.age))"
    }
    
    var fullAddress: String {
        return self.addressInfo.city + ", " + self.addressInfo.country
    }
    
    //JSON의 파라미터의 이름(키값)과 매칭되지 않을 때 CodingKey 프로토콜을 따르게 해 바꿔서 사용
    enum CodingKeys: String, CodingKey {
        case name, age
        case addressInfo = "address_info"
    }
}

Xcode 단축키

프로토콜 누르고 control^, command 누르면 UIKit를 보여준다.
command n: 새 파일 생성

화면 전환...

왜이렇게 나뉘어서 되는거지?
미치겠다...


좋은 웹페이지 즐겨찾기