본문 바로가기
Xcode/IOS

IOS) PickerView 에 대해서 알아보자!

by 후르륵짭짭 2020. 11. 12.
728x90
반응형

 

안녕하세요! 후르륵짭짭 입니다.

이번에는 Picker View에 대해 공부를 해봤습니다.

사실 Picker View를  사용할 일도 없었고 사용해 본적이 별로 없긴 합니다.

그래도 공부 할 필요가 있다고 생각해서 준비를 했습니다.

www.youtube.com/watch?v=8EuoHAw_PBk

 

** 영상의 내용 정리 **

일단 스토리보드를 사용해서 Picker View를 끌어서 놓어주고 Auto Layout을 해주세요!

그리고 이제 코드와 PickerView 까리 아울렛을 연결 시켜주시고 PickerView의 내용을 담을 클래스를 하나 생성 해주세요!

//PicerView Class

class DataModelPickerView: UIPickerView {

    var dataModels : [DataModel] = []
    var type : Bool = false
    var height : CGFloat = 150
    
//    convenience init(value : Bool) {
//        self.init(frame: CGRect.zero)
//              //assign custom vars
//        dataModels = Data.getData()
//    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        dataModels = Data.getData()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
//    override func layoutSubviews() {
//        super.layoutSubviews()
//        dataModels = Data.getData()
//    }

}

extension DataModelPickerView : UIPickerViewDelegate {
    
    //하나의 글만 보여 줄 때는 이러한 방법을 사용한다.
//    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
//        return dataModels[row].dayName
//    }
    
    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        return height
    }
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        
        let view = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: height))
        
        let stackview = UIStackView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: height))
        stackview.distribution = .fillEqually
        stackview.axis = .vertical
        stackview.spacing = 2
        
        let topLabel = UILabel()
        topLabel.text = dataModels[row].dayName
        topLabel.textColor = .black
        topLabel.textAlignment = .center
        topLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold)
        
        stackview.addArrangedSubview(topLabel)
        
        let midLabel = UILabel()
        midLabel.text = dataModels[row].price
        midLabel.textColor = .orange
        midLabel.textAlignment = .center
        midLabel.font = UIFont.systemFont(ofSize: 30, weight: .bold)

        stackview.addArrangedSubview(midLabel)

        let endLable = UILabel()
        endLable.text = dataModels[row].date
        endLable.textColor = .red
        endLable.textAlignment = .center
        endLable.font = UIFont.systemFont(ofSize: 20, weight: .bold)

        stackview.addArrangedSubview(endLable)
        
        view.addSubview(stackview)
        
        if type {
            view.transform = CGAffineTransform(rotationAngle: 90 * (.pi / 180))
        }
        
        return view
    }
    
}

extension DataModelPickerView : UIPickerViewDataSource {
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return dataModels.count
    }

}

이렇게 코드를 작성해주세요!

설명을 하도록 하겠습니다.

 

일단 PickerView도 TableVew와 비슷하게 delegate를 해주고 보여줄 갯수랑 어떻게 보여줄 것인지 두가지를 설정 해줘야합니다.

class DataModelPickerView: UIPickerView {,,,}

//보여주는 방식 설정
extension DataModelPickerView : UIPickerViewDelegate {

    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {,,,}
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {,,,}
    
}

//컬럼의 갯수 와 보여줄 갯수
extension DataModelPickerView : UIPickerViewDataSource {
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return dataModels.count
    }

}

이렇게 UIPickerViewDataSource와 UIPickerViewDelegate를 각각 만들어주세요!

 

DataSource에서는 필요한 정보를 Delegate에는 보여주는 방식을 정해줍니다.

func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

이것은 PickerView의 컬럼의 숫자를 말합니다. (휠? 쨋든 돌아가는거 갯수 ㅎㅎㅎ)

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return dataModels.count
    }

이것은 몇개를 보여줄 지 알려주는 것 입니다. 

 

이제 Delegate를 보도록 하겠습니다.

extension DataModelPickerView : UIPickerViewDelegate {
    
    //하나의 글만 보여 줄 때는 이러한 방법을 사용한다.
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return dataModels[row].dayName
    }
    
    //Cell의 높이를 정해준다.
    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        
    }
    
    //원하는 View를 꾸며서 보여준다.
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
     
    }
    
}

이렇게 할 수 있습니다.

 

** 어려웠던 부분 **

class ViewController: UIViewController {

    @IBOutlet weak var pickerView: UIPickerView!
    @IBOutlet weak var horiPickerView: UIPickerView!
    
    var dataModelPicker : DataModelPickerView!
    var dataModelHoriPicker : DataModelPickerView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    
        settingPickerView()
        settingHoriPickerView()
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        //왜 Frame을 여기에다가 해주면 오류가 발생하는지 알았다.
//        viewDidLayoutSubviews() 에서는 계속해서 Update Cycle 마다 생성하기 때문에 frame을 매번 변경해주는 것이다
        //그런데 frame = CGRect를 이미 오토레이아웃이 정해진 곳에서 사용하려면 viewdidLoad에서는 사용이 안된다.
        //왜냐하면 viewDidLoad에서는 view를 구성하지 않기 때문이다.
        //따라서 viewDidLayoutSubView에서 해줘야하는데 그러면 Picker View를 사용 할 때 마다 frame을 재구성하게 되서
        //오류가 발생한다. 벽돌현상이 되어 버린다.
    }
    
    
    func settingPickerView(){
        
        dataModelPicker = DataModelPickerView(frame: .zero)
        
        //pickerView의 기능을 dataModelPicker가 대신하겠다!
        pickerView.delegate = dataModelPicker
        pickerView.dataSource = dataModelPicker
        
        
    }
    
    func settingHoriPickerView(){
       
        let horiHeight = horiPickerView.frame.height
        let horiWidth = horiPickerView.frame.width
        
        dataModelHoriPicker = DataModelPickerView(frame: .zero)
        dataModelHoriPicker.type = true
        //let horiY = horiPickerView.frame.origin.y
       

        print(horiHeight / horiWidth)
        print(view.bounds.width / horiWidth)

        //X가 높이가 되고 Y가 가로 길이가 된다.
        horiPickerView.transform = CGAffineTransform(rotationAngle: -90 * (.pi / 180)).scaledBy(x: horiHeight / horiWidth, y: view.bounds.width / horiHeight)

        
        horiPickerView.delegate = dataModelHoriPicker
        horiPickerView.dataSource = dataModelHoriPicker
        
    }


}

이 PickerView를 하면서 곤란 했던 부분은  이 동영상에서는 바로 연결 시켜서 하는 것이 아니라 PickerView 클래스를 만들고 그 클래스를 delegate로 연결 시키는 방식 이였습니다.

1.

따라서 아래 처럼 convience init()을 사용 해야한다는 것을 알게 되었습니다.

class DataModelPickerView: UIPickerView {

    var dataModels : [DataModel] = []
    var type : Bool = false
    var height : CGFloat = 150
    
    convenience init() {
        self.init(frame: CGRect.zero)
              //assign custom vars
        dataModels = Data.getData()
   }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        dataModels = Data.getData()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

아니면 그냥 일반 적인 init(frame:)을 사용하되, zero를 보내주면 됩니다.

 

2.

AutoLayOut으로 설정된 View는 ViewDidLoad() 시점에서는 변경이 되지 않는다.

따라서 viewDidLayoutSubviews()에서 View를 재 구성 할 때,

.frame = CGRect()로 할 경우, 변경은 되지만, 휠을 돌리거나 View가 변경이 된다면 ViewDidLayOutSubView에서는 계속적으로 호출 되기 때문에 오류가 발생한다.

이러한 문제를 극복하기 위해 AutoLayout으로 구성된 View는 아래 처러 CGAffineTransform을 사용해줘야합니다.

//X가 높이가 되고 Y가 가로 길이가 된다.
        horiPickerView.transform = CGAffineTransform(rotationAngle: -90 * (.pi / 180)).scaledBy(x: horiHeight / horiWidth, y: view.bounds.width / horiHeight)

 

사실 어떻게 만드는 것은 youtube를 통해 확인 하는게 더 쉬울 것입니다.

따라서 저는 이 프로젝트를 진행하면서 발생 했던 오류를 담아 봤습니다.

감사합니다.

 

소스 코드 :

github.com/HururuekChapChap/Xcode_TestProj/tree/master/PickerView_Tutorial/PickerView_Tutorial

 

참고 사이트:

View의 init(frame:) 없이 init() 초기화 만들기

stackoverflow.com/a/44418686/13065642

 

How do I write a custom init for a UIView subclass in Swift?

Say I want to init a UIView subclass with a String and an Int. How would I do this in Swift if I'm just subclassing UIView? If I just make a custom init() function but the parameters are a String ...

stackoverflow.com

 

AutoLayOut으로 만들어진 Code를 통해 View를 변경하는 방법

stackoverflow.com/a/38287047/13065642

 

Why can't I change the view's frame size in Swift?

So this is the weirdest thing I came across ever. I add a simple UIView on top of an UIView on an UIViewController. I change the size to 200 x 200 and then I want to change the UIView's size by pl...

stackoverflow.com

 

CGAffainTransform을 여러개 적용하는 방법

stackoverflow.com/a/30929987/13065642

 

How to apply multiple transforms in Swift

I would like to apply multiple transforms to a UIView (or subclass of UIView), such as translate, rotate, and scale. I know that two transforms can be applied with CGAffineTransformConcat, but how ...

stackoverflow.com

PickerView에서 값이 변경되지 않는 문제 해결 방법

stackoverflow.com/questions/46417913/pickerview-default-row-selected-but-returns-zero-unless-the-picker-view-is-moved

 

PickerView Default row selected but returns zero unless the picker view is moved

In my picker view I want the default row to be zero. This row has a value of 1. I want to be able to touch nothing on the view contoller except a button. I know there are similar questions but t...

stackoverflow.com

 

728x90
반응형

댓글