안녕하세요! 후르륵짭짭 입니다.
이번에는 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
AutoLayOut으로 만들어진 Code를 통해 View를 변경하는 방법
stackoverflow.com/a/38287047/13065642
CGAffainTransform을 여러개 적용하는 방법
stackoverflow.com/a/30929987/13065642
PickerView에서 값이 변경되지 않는 문제 해결 방법
'Xcode > IOS' 카테고리의 다른 글
IOS) Local Notification을 이용해보자 2부 (0) | 2020.11.25 |
---|---|
IOS) Local Notification을 이용해보자 1부 (0) | 2020.11.22 |
IOS) NastedScrollView를 만들어 보자 (0) | 2020.11.02 |
IOS) 복사 & 붙여넣기를 구현해보자!!! (0) | 2020.09.16 |
IOS) UIGesture을 알아보자! (0) | 2020.08.25 |
댓글