SSAC iOS 앱 개발자 데뷔과정 - 13

Sangwon Shin·2021년 10월 18일
0

SSAC

목록 보기
11/19

📟 Protocol

프로토콜은 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다.

우리가 Table View 를 구현하기 위해서 TableView Controller 를 이용했습니다.

View ControllerTable ViewTable View Cell 을 추가하는 방법을 한 번 확인 해보겠습니다.

class SettingViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var settingTableView: UITableView!
    
    let settingList = [
        ["위치 알림 설정", "카메라 알림 설정"],
        ["교보 문고", "영풍 문고", "반디앤루이스"],
        ["앱스토어 리뷰 작성하기", "문의하기"]
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: DefaultTableViewCell.identifier, for: indexPath) as? DefaultTableViewCell else {
            return UITableViewCell()
            
        }
        
        cell.titleLabel.text = "설정 텍스트"
        return cell
    }
    
}

Table View 를 구현하기 위해서는

  • cellForRowAt
  • numberOfRowsInSection

를 필수적으로 구현해야 함을 블로그 에서 공부했었습니다.

그런데, ViewController 는 UIViewController 를 상속 받기 때문에 위의 메서드를 사용할 수 없습니다.

❓하지만 위의 코드를 보시면
UITableViewDelegate, UITableViewDataSource 를 적고 해당 메서드를 사용한 것을 확인할 수 있습니다. 어떻게 가능할까요?

Table view controllers already adopt the protocols you need to manage your table view's content and respond to changes.

공식문서를 확인해보면 UITableViewController 는 table view 의 내용을 관리하기 위해서 프로토콜을 채택했다고 합니다.

UITableViewDelegate, UITableViewDataSource 과 같은 프로토콜에서 cellForRowAt, numberOfRowsInSection 과 같은 메서드를 필수적으로 구현하도록 설계했기 때문에

우리는 위의 코드와 같이 해당 프로토콜을 채택함을 명시하고, 해당 메서드들을 구현해서 사용할 수 있습니다.

❗️ 그리고 프로토콜을 불러오고 연결해야 합니다(대리자 위임)(코드로도 가능)

🤮 설명만으로는 이해가 어렵죠.. 예시를 통해서 하나하나 확인해보겠습니다.

protocol NavigationUIProtocol {
    var titleString: String { get set } // 둘 다 사용하면 -> 둘 다 필수
    var mainTintColor: UIColor { get } //get 만 사용한 경우 -> get 은 필수, set 선택
    
}

우선 프로토콜 선언을 먼저 살펴보면, 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있습니다.

  • 프로토콜은 연산프로퍼티, 저장프로퍼티를 신경쓰지 않습니다.
  • 프로토콜은 요구만하고, 실제 구현은 해당 프로토콜을 채택하는 타입에서 합니다.
  • 항상 변수로 선언해야 합니다.
//저장 프로퍼티로 사용
class ShinViewController: UIViewController, NavigationUIProtocol {
    
    var titleString: String = "내 일기"
    var mainTintColor: UIColor = .black
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = titleString
        view.backgroundColor = mainTintColor
        
    }
}

// 연산 프로퍼티로 사용
class Shin2ViewController: UIViewController, NavigationUIProtocol {
    
    var titleString: String {
        get {
            return "My diary"
        }
        
        set {
            title = newValue
        }
    }
    
    //set 은 선택적으로 추가 가능 , get 생략가능
    var mainTintColor: UIColor {
        return .black
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        titleString = "My New Diary"
        
    }
    
}

해당 프로토콜을 채택한 클래스를 살펴 보겠습니다.
우리가 프로토콜을 선언할 때, set 을 설정하지 않더라도 클래스에서 set 을 구현할 수 있습니다.

@objc
protocol OrderSystem {
    func recommandEventMenu()
    @objc optional func askStampCard(count: Int) -> String //선택적 구현
}

class StarBucksMenu {
    
}

class Smoothie: StarBucksMenu, OrderSystem {
    func recommandEventMenu() {
        print("스무디 이벤트 안내")
    }
    
    func askStampCard(count: Int) -> String {
        return "\(count)잔 적립완료"
    }
    
}

class Coffee: StarBucksMenu, OrderSystem {
    
    let smoothie = Smoothie()
    
    func recommandEventMenu() {
        print("커피 이벤트 안내")
    }
    
    func askStampCard(count: Int) -> String {
        return "\(count)잔 적립완료"
    }  
}

메서드를 요구하는 프로토콜을 확인해보겠습니다.

프로토콜을 채택하는 타입은 프로토콜에 선언된 모든 메서드들을 구현해야 합니다
만약, optional 하게 사용하기 위해서는 @objc 를 사용해야 합니다.

❗️프로토콜에 생성자를 선언할 수 있습니다
❗️상속받은 프로토콜로 형변환이 가능합니다


⚔️ XIB

CustomTable View Cell 을 중복해서 사용해야 하는 상황이 있다고 가정 해보겠습니다.

지금까지 학습한 방법으로는 CustomTable View Cell 중복해서 만들어야 합니다. 이런 반복과정을 생략하기 위해서 XIB file 로 CustomTable View Cell 을 만들어야 합니다.

그림과 같이 XIB file 을 체크하고 cell 파일을 생성하게 되면,
tableViewCell 클래스와 XIB 파일이 함께 생성됩니다.

이때 생성된 XIB 파일에서 우리의 목적에 맞게 cell 을 디자인하고 사용 합니다.
❗️ 클래스 파일은 자동으로 연결되어 있지만, 사용을 위해서 identifier 설정!!

let nibName = UINib(nibName: "DefaultTableViewCell", bundle: nil)
settingTableView.register(nibName, forCellReuseIdentifier: DefaultTableViewCell.identifier)

❗️ 그리고 위와 같은 코드로 테이블뷰에서 우리가 만든 XIB 파일의 셀을 사용하겠다고 명시해줘야 합니다!

XIB = Xml Interface Builder 
NIB = Next Interface Builder
공부하겠읍니다.. 정리하겠읍니다..

📤 passData

바로 지난 시간에 화면전환에 대해서 공부했습니다.
지금까지 화면을 전환할 때 어떤 데이터를 공유하기 위해서 UserDefaults 를 사용했습니다.

userDefaults 를 사용하지 않고 view A 의 데이터를 view B 로 넘겨주는 방법을 알아보겠습니다.

그림과 같은 순서로

  • B 에서 데이터를 받을 공간을(변수) 설정합니다.
  • B 에서 해당 데이터를 표현합니다.
  • A 에서 B 로 값을 전달합니다.

왜 A 에서 B 로 값 전달 하는게 마지막인가요? 라고 의문이 들 수 있습니다.
코드를 함께 살펴 보면서 확인해보겠습니다.

import UIKit
//B 화면 설정
class CastTableViewController: UITableViewController {
    //1 ) 데이터를 받을 공간(변수) 설정
    var castSpace: TvShow?
    var castArray: Array<String> = []
    
    @IBOutlet weak var headerImageView: UIImageView!
    @IBOutlet weak var hearderLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 2 ) 데이터를 표현
        navigationItem.title = "출연/제작"        
        hearderLabel.text = castSpace?.title
        let url = URL(string: castSpace?.backdropImage ?? "")
        let data = try? Data(contentsOf: url!)
        headerImageView.image = UIImage(data: data!)
        
    }
    
    @objc
    func backButtonClicked() {
        navigationController?.popViewController(animated: true)
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return castArray.count
    }
    
    // 셀의 디자인 및 데이터 처리
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "CastTableViewCell", for: indexPath) as? CastTableViewCell else {
            return UITableViewCell()
        }
        
        let url = URL(string: castSpace?.backdropImage ?? "")
        let data = try? Data(contentsOf: url!)
        
        // 2) castArray 의 값들을 받아와서 설정
        cell.castImageView.image = UIImage(data: data!)
        cell.nameLabel.text = castArray[indexPath.row]
        cell.roleLabel.text = "저는 말하는 감자입니다..."
        
        return cell
        
    }
}

우선 A 화면으로 부터 TvShow 구조체 파일과 String 배열을 받아 오도록 변수를 설정했습니다.

TvShow 구조체를 담을 castSpace 를 이용해 tableView 의 header 영역 이미지뷰를 설정하고,
String 배열을 담을 castArray 를 이용해 label 에 출연진의 이름을 담으려고 합니다.
(출연진의 이름이 하나의 String 으로 구성되어 있어 split 해서 하나씩 나눠서 담을 예정입니다.)

그리고 A 화면 코드를 보겠습니다.

import UIKit

class MainTableViewController: UITableViewController {
    
    let tvShowInformation = TvShowInformation()
    
//셀을 클릭했을 때 기능
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let sb = UIStoryboard(name: "Main", bundle: nil)
        let vc = sb.instantiateViewController(withIdentifier: "CastTableViewController") as! CastTableViewController

// 3) A -> B 로 데이터를 넘겨준다.
        let row = tvShowInformation.tvShow[indexPath.row]
        vc.castSpace = row
        vc.castArray = row.starring.components(separatedBy: ",")
        
        navigationController?.pushViewController(vc, animated: true)
        //Navigation Contorller 스토리 보드에서 설정할 때도 현재 뷰에서 다음 뷰의 뒤로 가기를 설정했음
        navigationItem.backButtonTitle = "뒤로"
        
    }   
}

1), 2) 를 먼저 설정하게 되면 화면 전환 할 viewcontroller 를 호출하면 넘겨줄 변수를 저장할 프로퍼티를 호출 할 수 있기 때문에 비교적 간단하게 설정할 수 있습니다.
starring 에는 출연진의 이름이 "A, B, C ..." 형태로 저장되어 있기 때문에 ',' 를 기준으로 나눠서 각각을 String 배열에 저장하는 형태로 구현 했습니다.


🏷 P.S.

약 2시간뒤면 애플 이벤트 입니다...
공부 하다가 볼까 고민이 됩니다. 맥미니 m1x 나오면 다시 알바라도 해야겠습니다 ㅠㅠ

내일은 enum - caseIterable 에 대해서 공부하겠습니다.

추가적으로 미션도 구현하고 있는데 저번에도 한번 궁금했던 내용인데 만약 custum cell 에 구현된 버튼을 눌렀을 때 어떤 동작을 실행 시키려면, 액션을 cell 에 구현하는 것인지 view 에 구현해야 하는지 의문입니다. ㅠㅠ 
조금 더 공부해서 확실하게 개념이 서면 정리하도로고 하겠습니다.
profile
개발자가 되고싶어요

0개의 댓글