Xcode/IOS

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

후르륵짭짭 2020. 11. 12. 15:33
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
반응형