swift & iOS/ios & xcode

[ios 개발] 컬럼이 2개인 UIPickerView 구현하기

whale3 2022. 2. 11. 15:30

아래 사진처럼 UIPickerView의 덩어리를 컬럼 또는 컴포넌트라고 부른다. 그래서 아래의 피커뷰는 컬럼(컴포넌트)이 2개인 피커뷰이다. 피커뷰를 사용하려면 컬럼의 갯수에 상관없이 반드시 UIPickerViewDataSource, UIPickerViewDelegate 라는 프로토콜을 채택해야 한다. 

UIPickerViewDataSource에는 피커뷰의 컬럼(컴포넌트)을 몇 개로 할 것인지와 각 컬럼에는 몇 줄이나 필요한지 설정할 수 있도록 도와주는 메소드들이 있다. UIPickerViewDelegate에는 각 컴포넌트가 가지고 있는 줄에 들어갈 컨텐츠(문자열이거나 UIView), 줄의 너비, 높이를 설정할 수 있는 메소드들이 있고 특히 피커뷰에서 스크롤을 돌려서 어떤 한 데이터를 선택하면 그 데이터의 인덱스를 알려주는 메소드도 있다. 

 

이 피커뷰에 사용할 데이터는 아래처럼 생겼다. City 객체가 들어있는 배열을 사용할 것이다.

 

// CitiesManager.swift

struct CitiesManager {
    let cities = [
        City(name: "Seoul", tourAttractions: ["Nam Mt", "Gyeongbok-Gung", "Han River"]),
        City(name: "New York", tourAttractions: ["Times Square", "Central Park", "MOMA"])
    ]
}

struct City {
    let name: String
    let tourAttractions: [String]
}

 

먼저 UIPickerViewDataSource를 채택한 뷰 컨트롤러는 자기 자신을 피커뷰의 dataSource로 등록해야 한다. CitiesManager는 피커뷰에 보여줄 데이터가 들어있는 구조체이다. 그래서 뷰 컨트롤러가 피커뷰에 표시할 데이터와 UIPickerView 사이의 다리 역할을 한다고 보면 된다. 

 

내 데이터 - 뷰 컨트롤러 - UIPickerView

 

class ViewController: UIViewController, UIPickerViewDataSource {
    @IBOutlet weak var testPickerview: UIPickerView!
    @IBOutlet weak var pickerResultLabel: UILabel! // 피커뷰에서 선택된 값을 보여주기 위한 UILabel
    
    var citiesManager = CitiesManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        testPickerview.dataSource = self
    }
}

 

UIPickerViewDataSource를 채택한 뷰 컨트롤러에 아래의 두 가지 메소드를 구현한다. 어차피 이 메소드들은 필수이기 때문에 구현하지 않으면 경고 메시지가 나온다. 

 

// 피커뷰에 구현하고 싶은 컴포넌트의 갯수를 리턴하면 된다.
func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 2
}

// 각 컴포넌트에 들어갈 행의 갯수를 리턴하면 된다. 
// 나는 첫번째 컴포넌트에는 도시 이름들을, 두번째 컴포넌트에는 각 도시의 관광지를 보여줄 것이기 때문에 
// 아래처럼 컴포넌트 번호에 따라 행의 갯수가 각각 다르게 리턴되도록 하였다. 
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    if component == 0 {
        return citiesManager.cities.count
    } else {
    	// 0번 컴포넌트에서 선택된 행의 인덱스
        let selectedCity = testPickerview.selectedRow(inComponent: 0)
        
        return citiesManager.cities[selectedCity].tourAttractions.count
    }
}

 

그 다음 UIPickerViewDelegate 프로토콜을 채택하고 피커뷰의 delegate에 해당 뷰 컨트롤러를 등록한다. 

 

class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    @IBOutlet weak var testPickerview: UIPickerView!
    @IBOutlet weak var pickerResultLabel: UILabel!
    
    var citiesManager = CitiesManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        testPickerview.dataSource = self
        testPickerview.delegate = self
    }
}

 

UIPickerViewDelegate의 메소드 중에 아래 2개의 메소드를 구현한다. 메소드 이름은 pickerView로 똑같지만 parameter가 다르다. 

 

// 각 컴포넌트의 행마다 어떤 문자열을 보여줄지에 대한 코드를 작성한다.
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    // 0번째 컴포넌트의 행에는 도시 이름이 나오고,
    if component == 0 {
        return citiesManager.cities[row].name
    } else {
    	// 나머지 컴포넌트의 행에는 0번째 컴포넌트에서 선택한 도시에 대한
        // tourAttractions 배열의 값들이 나온다. 
        let selectedCity = testPickerview.selectedRow(inComponent: 0)
        return citiesManager.cities[selectedCity].tourAttractions[row]
    }
}

// 피커뷰의 스크롤을 움직여서 값이 선택되었을 때 호출되는 메소드. 
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    if component == 0 {
    	// 특정 컴포넌트의 행을 선택하게 하는 메소드이다.
    	// 그러므로 아래는 1번째 컴포넌트의 0번째 줄을 선택하게 된다.
        testPickerview.selectRow(0, inComponent: 1, animated: false)
    }

	// 선택한 값을 레이블이 보여주는 부분
    let cityIdx = testPickerview.selectedRow(inComponent: 0)
    let selectedCity = citiesManager.cities[cityIdx].name
    let tourIdx = testPickerview.selectedRow(inComponent: 1)
    let selectedTourAttraction = citiesManager.cities[cityIdx].tourAttractions[tourIdx]
    pickerResultLabel.text = "\(selectedTourAttraction), \(selectedCity)"

	// 16번째 줄과 같이 0번째 컴포넌트에서 변화가 감지되었다는 것은 
    // 1번째 컴포넌트에 보여주어야 할 데이터가 달라졌다는 것이므로
    // reloadComponent 메소드를 사용하여 1번째 컴포넌트를 업데이트 한다. 
    testPickerview.reloadComponent(1)
}

 

특히 마지막 줄의 testPickerview.reloadComponent(1)를 하지 않으면 이전 데이터가 그대로 노출된다. 

 

relocadComponent(1)을 하자

 

 

 

 

 

깃허브: https://github.com/lyj-ooz/ex-pickerView

 

GitHub - lyj-ooz/ex-pickerView: ios pickerView practice

ios pickerView practice. Contribute to lyj-ooz/ex-pickerView development by creating an account on GitHub.

github.com

 

반응형