Swift 5주차 - 멀티 컴포넌트 피커 뷰 만들기

2024. 4. 5. 21:37swift

Swift 5주차 - 멀티 컴포넌트 피커 뷰 만들기

 

이번 주차에서는 피커 뷰를 사용해 원하는 항목을 선택해보고, 컴포넌트를 추가하여 멀티 컴포넌트 피커 뷰까지 만들어 볼 예정이다.

 

목차

     

    피커 뷰(Picker View)란?

     

    피커 뷰(Picker View)는 아이폰에서 원하는 항목을 선택할 수 있게 해주는 객체다.

     

    지난시간에 사용했던 데이트 피커가 날짜와 시간을 선택하기 위한 객체라면, 피커 뷰는 문자열을 선택하기 위한 객체이다.

     

     

    프로젝트에 사진 추가하기

     

    먼저 프로젝트에서 사용할 사진 10개를 준비하자.

     

    필자는 간단하게 미국 1대 ~ 10대 대통령의 사진을 준비해봤다.

     

     

     

     

    xcode의 프로젝트로 들어와서 프로젝트 폴더를 우클릭하고 'New Group'을 선택하자.

     

     

     

     

    그룹 이름은 'images'로 지정하고 준비한 이미지를 그룹 내에 넣자.

     

     

    피커 뷰 앱 화면 꾸미기

     

    xcode 우측 상단의 '+' 버튼을 클릭한 후 'Picker View'를 찾아 선택한 후 스토리 보드로 끌어와 화면의 위쪽에 배치하자.

     

     

    이번에는 레이블 객체를 두개 가져와서 하나는 'Selected Item:' 이라고 입력한 후 중앙 왼쪽에 배치하고,

    다른 하나는 'Item' 이라고 입력하고 오른쪽에 배치하자.

     

     

     

     

    마지막으로 이미지 뷰 객체를 찾아서 스토리보드로 끌어온 후 화면 아래쪽에 배치하자.

     

     

    오른쪽 인스펙터 영역에서 Content Mode를 'Aspect Fill'로 변경하자.

     

    Aspect Fill은 원래 사진의 비율은 유지한 채 사진을 확대하거나 축소하여 이미지 뷰의 크기에 맞게 맞춰준다.

     

     

    아웃렛 변수 추가하고 델리게이트 설정하기

     

    피커 뷰에 대한 아웃렛 변수를 추가해보자.

     

     

    이름은 'pickerView'로 설정하고 연결하자.

     

     

    그 다음 Item 레이블에 대한 아웃렛 변수도 추가하자.

     

     

    이름은 'lblImageFileName'로 설정하자.

     

     

    마지막으로 이미지 뷰에 대한 아웃렛 변수를 추가해보자.

     

     

    이름은 'imageView'로 설정하자.

     

     

    델리게이트(Delegate)란? (피커 뷰 델리게이트 설정하기)

     

    피커 뷰가 상호 작용하려면 피커 뷰에 대한 델리게이트 메서드를 사용해야 한다.

     

    델리게이트(Delegate)는 대리자 라고도 하며 누군가 해야할 일을 대신 해주는 역할을 한다.

    예를 들어 특정 객체와 상호작용할 때 메세지를 넘기면 그 메세지에 대한 책임은 델리게이트로 위임된다.

    그리고 델리게이트 메서드는 해당 역할을 수행하며 처리 결과나 메세지 등을 받는다.

    즉, 사용자가 객체를 터치했을 때 해야 할 일을 델리게이트 메서드에 구현하고 해당 객체가 터치되었을 때 델리게이트가 호출되어 위임받은 일을 하게 되는 것이다.

     

    이렇게 델리게이트를 사용하는 이유는 객체지향프로그래밍에서 하나의 객체에 모든 일을 맏기는 것이 아니라 일을 나누어 다른 객체에게 위임하여 특정 객체의 부담을 줄이기 위함이다.

     

    이렇게 하면 객체 간의 결합도가 낮아져 개발자가 유지보수하기 쉽고 객체 간의 상호작용을 유연하게 조정할 수 있게 된다.

     

     

     

    이제 피커 뷰의 델리게이트를 사용을 설정해보자.

     

     

    피커 뷰를 선택한 후 마우스로 우클릭 한 상태로 위쪽의 뷰 컨트롤러 아이콘으로 쭉 끌어다 놓아보자.

     

     

     

    위 사진처럼 선택화면이 나요면 'delegate'를 선택해주면 된다.

     

     

    피커 뷰 동작 코드 작성하기

     

    피커 뷰의 델리게이트 메서드를 사용하려면 'UIPickerViewDelegate' 클래스와 'UIPickerViewDateSource' 클래스를 상속받아야 한다.

     

    ViewController 클래스 선언문 오른쪽에 UIPickerViewDelegate, UIPickerViewDateSource 를 입력하면 클래스를 상속받을 수 있다.

     

     

    저 두 클래스를 추가로 입력하면 에러 메세지가 나타나는데 이는 델리게이트 메서드가 없어서 발생한 것인데 나중에 추가할 것이니 지금은 무시해도 된다.

     

     

    피커 뷰가 동작하는데 필요한 변수 및 상수를 추가해 보자.

     

    ViewController 클래스 선언부와 아웃렛 변수 선언부 사이에 추가하면 된다.

     

    let MAX_ARRAY_NUM = 10 // 1
    let PICKER_VIEW_COLUMN = 1 // 2
    var imageFileName = [ "1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg",
                          "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg" ] // 3

     

    1. 사용할 이미지의 개수를 지정한다.
    2. 피커 뷰의 열의 개수를 지정한다.
    3. 이미지의 파일명을 저장할한 배열이다.

     

     

    그 다음으로 피커 뷰에게 무엇을 보여주고 어떻게 동작할지를 설정해보자.

     

    피커 뷰의 동작에 필요한 델리게이트 메서드를 뷰 컨트롤러 클래스의 맨 아래에 추가하자.

     

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return PICKER_VIEW_COLUMN // 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return imageFileName.count // 2
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return imageFileName[row] // 3
    }

     

    1. 피커 뷰에게 컴포넌트의 수를 정수 값으로 넘겨주는 델리게이트 메서드이다. 피커 뷰의 컴포넌트는 피커 뷰에 표시되는 열의 개수를 의미한다.
    2. numberOfRowsInComponent 인수를 가지는 델리게이트 메서드이다. 피커 뷰에게 컴포넌트의 열의 개수를 정수값으로 넘겨준다.
    3. titleForRow 인수를 가지는 데릴게이트 메서드이다. 피커 뷰에게 컴포넌트의 각 열의 타이틀을 문자열(String)값으로 넘겨준다.

     

     

    실행 버튼을 클릭하여 앱을 실행해보자.

     

    피커 뷰에 각 행의 타이틀 1 ~ 10.jpg가 보이는 것을 확인할 수 있다.

     

    아직 피커 뷰의 룰렛을 돌려봐도 다른 변화는 없다.

     

     

    선택한 이미지 이름과 해당 이미지 출력하기

     

    앞에서 피커 뷰에 지정한 타이틀로 선택목록이 보이고, 피커 뷰의 룰렛을 돌릴 수 있다는 것을 확인했다.

     

    이제 피커 뷰로 선택한 이미지 이름과 해당 이미지를 이미지 뷰에 출력하는 코드를 작성해보자.

     

     

    피커 뷰의 룰렛을 돌려 특정 열을 선택했을 때 할 일을 델리게이트에게 지시하는 메서드를 맨 아래에 추가하자.

     

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        lblImageFileName.text = imageFileName[row]
    }

     

    피커 뷰의 룰렛에서 선택한 row값을 사용하여 imageFileName 배열에서 row값에 해당하는 문자열을 가지고 와 lblImageFileName.text에 저장한다.

     

     

    실행버튼을 클릭하여 피커 뷰의 룰렛을 돌려보면 레이블에 파일명이 출력되는 것을 확인할 수 있다.

     

     

     

    이제 피커 뷰의 룰렛을 선택할 경우 선택된 파일명에 해당하는 이미지를 이미지 뷰에 출력해보자.

     

    먼저 앞에서 변수를 추가한 위치에 UIImage 타입의 배열 imageArray를 선언하자.

     

    let MAX_ARRAY_NUM = 10
    let PICKER_VIEW_COLUMN = 1
    var imageArray = [UIImage?]()
    var imageFileName = [ "1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg",
                          "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg" ]

     

     

    그 다음 viewDidLoad 함수 안에 아래 코드를 추가하자.

     

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        for i in 0 ..< MAX_ARRAY_NUM { // 1
            let image = UIImage(named: imageFileName[i]) // 2
            imageArray.append(image) // 3
        }
       
        lblImageFileName.text = imageFileName[0] // 4
        imageView.image = imageArray[0] // 5
    }

     

    1. i라는 변수를 0부터 MAX_ARRAY_NUM보다 작을 때까지 반복한다.
    2. imageFileName[i]에 있는 파일명을 사용하여 UIImage타입의 이미지를 생성하고 image라는 변수에 할당한다.
    3. imageArray 배열에 방금 만든 image를 추가한다.
    4. lblImageFileName 레이블에 ImageFileName 배열의 첫 번째 파일명을 출력한다.
    5. 이미지 뷰에 첫번째 이미지를 나타낸다.

     

    마지막으로 피커 뷰의 룰렛이 선택되었을 때 동작하는 didSelectRow 인수가 포함된 델리게이트 메서드의 맨 아랫부분에 선택한 이미지를 이미지 뷰에 나타내주는 코드를 추가하자.

     

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        lblImageFileName.text = imageFileName[row]
        imageView.image = imageArray[row] // 1
    }

     

    1. 피커 뷰의 룰렛에서 선택한 row값을 사용하여 이미지를 가져와 imageView.image에 저장한다.

     

     

    실행버튼을 클릭하여 결과를 확인해보자.

     

     

     

    피커 뷰의 룰렛에서 선택한 파일명은 레이블에, 이미지는 이미지 뷰에 나타나는 것을 확인할 수 있다.

     

     

    피커 뷰 룰렛에 이미지 출력하기

     

    이번에는 피커 뷰 룰렛에 파일명 대신 이미지가 나타나도록 구현해보자.

     

    앞에서 선언한 titleForRow 인수를 가지는 델리게이트 메서드를 주석처리 하자.

     

     

     

    viewForRow 인수가 포함되어 있는 델리게이트 메서드를 새로 추가하자.

     

    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { // 1
        let imageView = UIImageView(image: imageArray[row]) // 2
        imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 150) // 3
        
        return imageView // 4
    }

     

    1. 피커 뷰에게 컴포넌트의 각 열의 뷰를 UIView 타입의 값으로 넘겨준다. 여기서는 이미지 뷰에 저장되어 있는 이미지를 넘겨준다.
    2. 선택된 row에 해당하는 이미지를 imageArray에서 가져온다.
    3. 이미지 뷰의 프레임 크기를 설정한다.
    4. 이미지 뷰를 리턴한다.

     

    실행버튼을 클릭하여 결과를 확인해보자.

     

     

    피커 뷰의 룰렛에 이미지의 파일명 대신 이미지가 출력되는 것을 확인할 수 있다.

     

     

     

    피커 뷰에서 보이는 이미지의 세로 높이가 좁아 이미지가 잘 안보이는 것 같으니 피커 뷰 룰렛의 세로 높이를 변경해보자.

     

    피커 뷰의 높이를 지정할 상수 'PICKER_VIEW_HEIGHT'를 추가하자.

     

    let MAX_ARRAY_NUM = 10
    let PICKER_VIEW_COLUMN = 1
    let PICKER_VIEW_HEIGHT:CGFloat = 80
    var imageArray = [UIImage?]()
    var imageFileName = [ "1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg",
                          "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg" ]

     

     

    피커 뷰의 높이를 전달할 피커 뷰 델리게이트 메서드를 numberOfComponents 메서드 아래에 추가하자.

     

    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        return PICKER_VIEW_HEIGHT
    }

     

     

    실행 버튼을 눌러 최종 결과를 확인해보자.

     

     

     

    피커 뷰 앱 전체 소스 코드

     

    import UIKit
    
    class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
        
        let MAX_ARRAY_NUM = 10
        let PICKER_VIEW_COLUMN = 1
        let PICKER_VIEW_HEIGHT:CGFloat = 80
        var imageArray = [UIImage?]()
        var imageFileName = [ "1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg",
                              "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg" ]
        
        @IBOutlet var pickerImage: UIPickerView!
        @IBOutlet var lblImageFileName: UILabel!
        @IBOutlet var imageView: UIImageView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            for i in 0 ..< MAX_ARRAY_NUM {
                let image = UIImage(named: imageFileName[i])
                imageArray.append(image)
            }
            
            lblImageFileName.text = imageFileName[0]
            imageView.image = imageArray[0]
        }
        
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return PICKER_VIEW_COLUMN
        }
        
        func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
            return PICKER_VIEW_HEIGHT
        }
        
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return imageFileName.count
        }
        
    //    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    //        return imageFileName[row]
    //    }
        
        func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
            let imageView = UIImageView(image: imageArray[row])
            imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 150)
            
            return imageView
        }
        
        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            lblImageFileName.text = imageFileName[row]
            imageView.image = imageArray[row]
        }
    }

     

     

     

    멀티 컴포넌트 피커 뷰 만들기

     

    지금까지 배운 내용을 토대로 여러개의 컴포넌트를 가진 피커 뷰를 만들어 보자.

     

    조건:

    1. 컴포넌트 개수를 2개로 변경해보자.
    2. 왼쪽 컴포넌트를 선택했을 때는 레이블에 파일명을 출력하고, 오른쪽 컴포넌트를 선택했을 때는 이미지 뷰에 이미지를 출력해보자.

     

     

    먼저 컴포넌트의 개수를 수정하는 것은 간단하다.

     

    PICKER_VIEW_COLUMN 변수의 값을 2로 하면 피커 뷰의 컴포넌트가 두개로 나타나게 된다.

     

    let PICKER_VIEW_COLUMN = 2

     

     

     

     

    그 다음으로 각각의 컴포넌트가 서로 다른 동작을 하도록 수정해보자.

     

    didSelectRow 인수가 포함되어 있는 델리게이트 메서드의 코드를 수정하자.

     

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        if (component == 0) {
            lblImageFileName.text = imageFileName[row]
        }
        else {
            imageView.image = imageArray[row]
        }
    }

     

    컴포넌트는 피커 뷰의 컴포넌트 인수를 사용해서 구분할 수 있다.

     

    컴포넌트는 왼쪽부터 순서대로 0, 1, 2, ••• 의 값을 갖는다.

     

     

    즉, 코드를 해석해보면 컴포넌트가 0이면(왼쪽) 레이블의 텍스트를 수정하고

    컴포넌트가 0이 아니라면(오른쪽) 이미지를 수정한다.

     

     

    실행버튼을 클릭하여 확인해보자.

     

     

    왼쪽 컴포넌트를 선택하면 파일명이 바뀌고, 오른쪽 컴포넌트를 선택하면 이미지가 바뀌는 것을 확인할 수 있다.

     

     

    import UIKit
    
    class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
        
        let MAX_ARRAY_NUM = 10
        let PICKER_VIEW_COLUMN = 2
        let PICKER_VIEW_HEIGHT:CGFloat = 80
        var imageArray = [UIImage?]()
        var imageFileName = [ "1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg",
                              "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg" ]
        
        @IBOutlet var pickerImage: UIPickerView!
        @IBOutlet var lblImageFileName: UILabel!
        @IBOutlet var imageView: UIImageView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            for i in 0 ..< MAX_ARRAY_NUM {
                let image = UIImage(named: imageFileName[i])
                imageArray.append(image)
            }
            
            lblImageFileName.text = imageFileName[0]
            imageView.image = imageArray[0]
        }
        
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return PICKER_VIEW_COLUMN
        }
        
        func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
            return PICKER_VIEW_HEIGHT
        }
        
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return imageFileName.count
        }
        
    //    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    //        return imageFileName[row]
    //    }
        
        func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
            let imageView = UIImageView(image: imageArray[row])
            imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 150)
            
            return imageView
        }
        
        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            if (component == 0) {
                lblImageFileName.text = imageFileName[row]
            }
            else {
                imageView.image = imageArray[row]
            }
        }
    }