• 카테고리

    질문 & 답변
  • 세부 분야

    모바일 앱 개발

  • 해결 여부

    미해결

환율 강의 테이블뷰에서 데이터 관련 질문있습니다.

23.03.15 00:33 작성 23.03.15 00:38 수정 조회수 373

0

안녕하세요 강의진행하면서 궁금한게있어서 질문드립니다.

저의 경우 처음 Picker 뷰에서 rates배열에 url로 데이터 파싱한 데이터를 Table탭에서 delegate패턴을 이용해서 가져오는식으로 데이터를 가져오려 했습니다.

먼저 Picker코드입니다.


//
//  ViewController.swift
//  Exchange Rate
//
// 
//

import UIKit

class PickerViewController: UIViewController {

    
    @IBOutlet weak var usdTextField: UITextField!
    @IBOutlet weak var selectedCurrency: UITextField!
    @IBOutlet weak var selectedCurrencyName: UILabel!
    
    @IBOutlet weak var currencyPicker: UIPickerView!
    
    var rates: [(String,Double)]?
    
    
//    table data delegate
    weak var delegate: tableDataDelegate?
    
//    picker row변화에 따른 observer property 계산
    var selectedRow = 0 {
        didSet {
            selectedCurrencyName.text = rates?[selectedRow].0

            selectedCurrency.text =  calculateCurrency()
        }
    }
    
    func calculateCurrency() -> String {
        let selectedValue = rates?[selectedRow].1 ?? 0
        let usdValue = Double(usdTextField.text ?? "") ?? 0
        
        let resultValue = String(format: "%.2f", (selectedValue * usdValue))
        return resultValue
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.navigationItem.title = "Currency Converter Picker"

        fetchJson()
        
        //picker 연결
        currencyPicker.delegate = self
        currencyPicker.dataSource = self
        
//        textField delegate
        usdTextField.delegate = self
        
//        porotcol delegate
        
    }
    
    func fetchJson() {
        let urlString = "https://open.er-api.com/v6/latest/USD"
        guard let url = URL(string: urlString) else {return}
        
        
//        data task
        URLSession.shared.dataTask(with: url) { data, res, err in
            guard let data = data else {return}
            
            do {
                let currencyModel = try JSONDecoder().decode(CurrencyModel.self,from: data)
        //                rates: [key:value] 형태
//                let rates = currencyModel.rates?.map { [$0 : $1] }
//                let rates2 :[(String,Double)]? = currencyModel.rates?.sorted(by: { dic1, dic2  in
//                    dic1.key < dic2.key // sorted같은걸하면 Dictionary.Element가되서 tuple로 바뀜
//                })
                self.rates = currencyModel.rates?.sorted{
                    $0.key<$1.key
                }
                
                
//                picker 새로고침해야 데이터 내용이 보임
                DispatchQueue.main.async {
                    self.currencyPicker.reloadAllComponents()
                    
                }
//                get table data
                print("rate: ",self.rates!)
                self.delegate?.getData(data: self.rates! )
                
//                print("currentModel",currencyModel)
            } catch {
                print(err!)
            }
        }.resume()
    }


}

extension PickerViewController:UIPickerViewDelegate,UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return rates?.count ?? 0
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        
        let key = rates?[row].0 ?? ""
        let value = rates?[row].1.description ?? ""

        
        return key + "  " + value
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            selectedRow = row
   
    }
}

extension PickerViewController:UITextFieldDelegate {
    func textFieldDidChangeSelection(_ textField: UITextField) {
        selectedCurrency.text =  calculateCurrency()

    }
}

저의 경우

             DispatchQueue.main.async {
                    self.currencyPicker.reloadAllComponents()
                    
                }
//                get table data
                print("rate: ",self.rates!)
                self.delegate?.getData(data: self.rates! )

여기서 rates가 전부불어와지면 delegate를 이용해서 해당데이터를 table뷰로 가져오려고 의도했습니다.

 

table데이터입니다.

//
//  ListViewController.swift
//  Exchange Rate
//
//

import UIKit

protocol tableDataDelegate:AnyObject {
    func getData(data:[(String,Double)])
}

class ListViewController: UIViewController {

    
    //    Data list
    var conData = [(String,Double)?]()

    
    @IBOutlet weak var udsLabel: UILabel!
    @IBOutlet weak var costTextField: UITextField!
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.navigationItem.title = "Currency Converter Table"
        self.udsLabel.text = "UDS"
        
//        table
//        tableView.delegate = self
        tableView.dataSource = self
        
        let sb = UIStoryboard(name: "Main", bundle: nil)
        guard let detailVC =  sb.instantiateViewController(withIdentifier: "PickerViewController") as? PickerViewController else {return}
        
        detailVC.delegate = self
        
        tableView.rowHeight = 100
    }
}

extension ListViewController:UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print("cor: ",conData)
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = self.tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as! MyTableViewCell
       
        
        cell.currencyLabel.text = "good"
        cell.valueLabel.text = "allaal"
            
            
        
        return cell
    }
    
    
}

extension ListViewController: tableDataDelegate{
    
    func getData(data: [(String,Double)]) {
        print("data: ",data)
        self.conData = data
//        print(self.conData)

    }
}

여기서 delegate프로토콜을 만들고 Picker뷰에서 가져온 데이터를 conData라는 배열에 담아서 저는 담겨진 데이터를 이용해서 테이블뷰에 데이터를 뿌리는식으로 작업하려했는데 이런식으로 하니 프로토콜이 제대로 안되서인지 conData에 데이터가 담기지 않고 빈배열이 나옵니다..

저의경우 table뷰에서 델리게이트를 사용한다고 위임하는 코드를

       let sb = UIStoryboard(name: "Main", bundle: nil)
        guard let detailVC =  sb.instantiateViewController(withIdentifier: "PickerViewController") as? PickerViewController else {return}
        
        detailVC.delegate = self

이렇게 Main에있는 스토리보드를 가져와서 권환을 위임했는데 이게 틀린걸까요?

어디가 잘못됐는지 잘모르겠습니다.. 답변 부탁드립니다!

답변 2

·

답변을 작성해보세요.

0

수열임님의 프로필

수열임

질문자

2023.03.16

안녕하세요 강사님이 말씀해주신대로 tabBarController에있는 viewControllers를 이용해 PickerViewController 인스턴스 myVC를 만들고 PickViewController에 저장된 rates(api정보가 담긴 배열) 을 ListViewController에있는 conData배열에 담는식으로 코드를 작성해봤습니다.

class ListViewController: UIViewController {

    
    //    Data list
    var conData = [(String,Double)?]()

    
    @IBOutlet weak var udsLabel: UILabel!
    @IBOutlet weak var costTextField: UITextField!
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.navigationItem.title = "Currency Converter Table"
        self.udsLabel.text = "UDS"
        
//        table
//        tableView.delegate = self
        tableView.dataSource = self
        
  
        
//        print(tabBarController?.viewControllers![0].topViewController)
        let myVC = tabBarController?.viewControllers![1] as! PickerViewController
//        print("m",myVC)
        conData = myVC.rates!
        tableView.rowHeight = 100
    }
    
}

일단 자동완성기능으로    conData = myVC.rates! 이부분은 에러가 나지않는걸 보니 myVC에서 PickerViewController에 관한 타입캐스팅은 성공한걸로 생각하는데 콘솔에찍히는 에러가
스크린샷 2023-03-16 오전 2.08.19.png이러한 에러가 나면서 안됩니다..

강사님이 tabBarController로 VC로 접근하라는말씀이
저는 테이블을 보여줄 VC인 ListViewController에서 tabBarController를 이용해 PickerViewController를 타입캐스팅하고 해당 타입캐스팅된 인스턴스에서의 rates배열의 정보를 테이블뷰에 있는 프로퍼티인 conData에 저장을 시켜서 해서 conData를 이용해서 화면을 구성라는식으로 이해했는데 제가 제대로 이해한게 맞을까요?

그리고 viewController공식문서를 보니 navigation Stack을이용해서 하위 컨트롤러에관한 정보를 갖고오는걸로 이해했습니다. 궁금한게 현재 저희가 만든 tabBarController하위의 컨트롤러는 PickerViewController와 ListViewController 2가지가 있는상태인데 스토리보드로 2개의 컨트롤러를만든경우 어떤게 먼저 스택에 쌓이는건가요? 제가 생각했을떄는 맨처음 화면에 나오는게 PickViewController니까 이게 0번이고 그다음 나오는게 ListViewController이기에 이게 1번 스택으로 쌓이는걸로 이해했는데 이게 맞을까요?

아 네 보니까 그냥 ViewController가 아니고 NavigationController에 감싸진 ViewController였네요.

VC는 Navigation의 탑레벨에 있는 VC니깐 아래처럼 가져올 수 있습니다.

let naviController = self.tabBarController?.viewControllers![1] as! UINavigationController
let myVC = naviController.topViewController as! MyViewController

 

 

수열임님의 프로필

수열임

질문자

2023.03.16

답변 감사합니다

0

안녕하세요

 

우선 instantiateViewController로 생성하는 인스턴스는 새로 만들어지는 것입니다.
그러므로 detailVC는 기존에 있는걸 불러오는 개념이 아니고 새로 만들어지는 새로운 것입니다.

만약 다른 탭과 데이터를 주고 받는걸 delegate패턴으로 하고 싶다면 참조하고 있는 연결점이 있어야 됩니다.
그런데 table쪽 VC와 picker쪽 VC는 탭으로 분리되어 있으며, 서로 연결점이 없습니다.

이럴땐 연결점이 있는 TabbarController를 이용할 수 있을 것 같습니다.

TabBarController에 delegate를 만들거나 TabBarController가 가진 정보로 접근하거나 하는 방식이 있을 것 같네요
또는 notificationCenter를 이용해서 데이터를 전달하는 방법을 이용할수도 있습니다.

 

간단하게 TabBarController가 가진 VC들에 접근하는 방식은 아래처럼 할 수 있습니다.

let myVC = tabBarController?.viewControllers![1] as? MyViewController

 

 

우선 질문의 delegateMove의 펑션은 데이터를 전달하는 것이 아닙니다.

여기서 delegate 타입을 잘 기억하시면 쉽게 이해가 될 것 같습니다.

detailVC.delegate = self

위 코드에서 delegate변수는 protocol타입이고 이 뜻은 protocol의 규격만 받을 수 있다는걸 뜻합니다.

이 뜻은 VC(self)에서 protocol규격으로 구현한 것만 전달하게 된다는 의미 입니다.

 

그리고 DetailVC로 넘어가서

아래처럼 만든 delegate변수가 있을 텐데요.

var delegate: protocol타입?

이 delegate변수 안에는 VC에서 protocol을 구현한 내용이 들어가 있게 되겠지요.

그래서 delegate.xxxx 이렇게 protocol의 내용을 호출하면 VC의 protocol의 규격에 맞춰 구현한 내용(func)이 호출되는 것입니다.

 


질문내용의 코드에서 VC와 DetailVC의 관계를 보자면

VC에서 DetailVC의 인스턴스를 만들고 화면에 띄웁니다.

이 뜻은 VC에서 DetailVC의 인스턴스를 만들었기 때문에 DetailVC의 정보를 알 수 있고, DetailVC에 정보도 전달해줄 수 있습니다.

그런데 이와는 다르게
TabBarController로 구현된 두개의 VC는 서로가 서로를 알 수 있는 연결점이 없습니다.
TabBar를 통해 만들어졌기 때문이죠.

 

아래 이미지를 보시면 이해가 더 잘되지 않을까 싶습니다.

VC1과 VC2는 TabBarController에서 인스턴스를 만들고 띄운거기 때문에 VC1,VC2의 인스턴스 정보는 TabBarController만 가지고 있습니다.

그래서 다른 탭의 VC에 접근하려면 TabBarController를 통해 접근이 가능하다고 말씀드린 부분입니다.

image이해 안되시면 더 질문주세요~

수열임님의 프로필

수열임

질문자

2023.03.15

헉.. 제가 괜한 질문을 한거같아서 질문글 지웠는데 답글 달아주셔서 감사합니다

delegate패턴을 이용한 데이터전달에 대해서 배운것을 정리해보고 있는데 이부분이 많이어렵네요 ㅠㅠ
일단은 혼자 정리해보고 막히는부분이 있으면 다시 질문드리겠습니다 답변해주셔서 감사합니다!