꾸준히 안타치기

② Picker View - customPlist / 커스텀 본문

iOS/기본편 | 실전편 -꼼꼼한재은씨

② Picker View - customPlist / 커스텀

글자줍기 2022. 2. 12. 18:22
반응형

피커뷰 구현하기 ( 계정 텍스트뷰를 누르면 화면하단에 피커뷰 등장 )

- 피커뷰는 셀렉트나 콤보박스 등의 기능을 하는 입력용뷰이다. 이미지피커뷰와는 전혀연관이 없다.

- 피켜뷰와 테이블뷰는 유사하다. 차이점은 테이블뷰는 UITableViewCell객체를 생성해야하는것 외에 유사함.

- 피커뷰는 문자열을 아이템으로 갖는 배열 타입의 데이터 소스를 사용한다.

메인스토리보드를 열고 테이블뷰추가 +네비게이션 컨트롤러 임베디드한다.

테이블뷰 셀을 정적으로 변경

테이블뷰 셀 추가

아래와 같이 화면구성하기

텍스트필드속성을 테두리없음 / 오른쪽 정렬로 변경

UserDefaluts  계정목록은 공통정보 이므로, 기본저장소에서 관리 계정목록 저장
Plist 데이터를 분리하여 저장할때 편리 / 계정별로 반복되는 정보를 따로 저장해야할때 사용한다.
UserDefaluts는 키가 같으면 덮어쓰기기 됨
앱에서 마지막으로 선택된 계정정보
UserDefalut (accountList에 저장)
A@email.com
B@email.com
C@email.com

 

Plist (selectedAccount에 저장)
A.plist B.plist C.plist
key value key value key value
account A account B account C
email A@email.com email B@email.com email C@email.com
updateFlag false updateFlag true updateFlag false

현재선택되어 있는 계정에 맞는 커스텀 Plist 파일을 읽어와야한다. / 데이터 저장하기 

선택된 사용자정보 가져오기

1. UserDefaults 코드를 삭제하고, 이름을 커스텀프로퍼티 리스트에 저장한다.

    //테이블뷰에 대한 델리게이트 메소드를 추가.
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        if indexPath.row == 1 && !(self.account.text?.isEmpty)!{ //두번째 셀이 클릭되었을때
            //입력이 가능한 알림창을 띄워 이름을 수정할수 있게한다.
            let alert = UIAlertController(title: nil, message: "이름을 입력하세요", preferredStyle: .alert)
            //입력필드 추가
            alert.addTextField(){
                $0.text = self.name.text // 이름레이블의 텍스트 넣어주기
            }
            
            // 버튼 및 액션 추가
            alert.addAction(UIAlertAction(title: "OK", style: .default){(_) in
                
                //사용자가 OK버튼을 누르면 입력 필드에 입력된 값을 저장한다.
                let value = alert.textFields?[0].text
                            
                // 커스텀프로퍼티에 저장$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                // 현재입력된 계정을 바탕으로 읽어올 커스텀 프로퍼티 파일명을 정의한다.
                let customPlist = "\(self.account.text!).plist" // 읽어올 파일명
                // 앱 내에 생성된 문서 디렉터리 경로를 구한다.
                let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
                let path = paths[0] as NSString
                let plist = path.strings(byAppendingPaths: [customPlist]).first!
                //읽어온 파일을 딕셔너리 객체로 변환한다. 만약 해당위치에 파일이 없다면 새로운 딕셔너리 객체를 생성한다.
                let data = NSMutableDictionary(contentsOfFile: plist) ?? NSMutableDictionary()
                // 입력된 이름값을 딕셔너리 객체에 "name"키로 저장한다.
                data.setValue(value, forKey: "name")
                // 딕셔너리객체를 커스텀 프로퍼티 파일로 저장한다.
                data.write(toFile: plist, atomically: true)
                
                self.name.text = value
            })
            //알림창띄움
            self.present(alert, animated: false, completion: nil)
        }
    }

2. 성별과, 결혼여부도 커스텀프로퍼티리스트에  gender,married키로 저장

    // 성별액션메소드
    @IBAction func changeGender(_ sender: UISegmentedControl) {
        let value = sender.selectedSegmentIndex //0이면 남자, 1이면 여자
        
//        let plist = UserDefaults.standard // 기본객체저장소를 가져옴
//        plist.set(value, forKey: "gender")//젠더라는키로 가져옴
//        plist.synchronize()// 동기화
        
        // 커스텀프로퍼티에 저장$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
        let customPlist = "\(self.account.text!).plist" // 읽어올파일명
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let path = paths[0] as NSString
        let plist = path.strings(byAppendingPaths: [customPlist]).first!
        //읽어온 파일을 딕셔너리 객체로 변환한다. 만약 해당위치에 파일이 없다면 새로운 딕셔너리 객체를 생성한다.
        let data = NSMutableDictionary(contentsOfFile: plist) ?? NSMutableDictionary()
        // 입력된 이름값을 딕셔너리 객체에 "name"키로 저장한다.
        data.setValue(value, forKey: "gender")
        // 딕셔너리객체를 커스텀 프로퍼티 파일로 저장한다.
        data.write(toFile: plist, atomically: true)
    }
    
    // 결혼여부액션메소드
    @IBAction func changeMarried(_ sender: UISwitch) {
        let value = sender.isOn //트루면 기혼
        
//        let plist = UserDefaults.standard
//        plist.set(value, forKey: "married")
//        plist.synchronize()
        
        // 커스텀프로퍼티에 저장$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
        let customPlist = "\(self.account.text!).plist" // 읽어올파일명
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let path = paths[0] as NSString
        let plist = path.strings(byAppendingPaths: [customPlist]).first!
        //읽어온 파일을 딕셔너리 객체로 변환한다. 만약 해당위치에 파일이 없다면 새로운 딕셔너리 객체를 생성한다.
        let data = NSMutableDictionary(contentsOfFile: plist) ?? NSMutableDictionary()
        // 입력된 이름값을 딕셔너리 객체에 "name"키로 저장한다.
        data.setValue(value, forKey: "married")
        // 딕셔너리객체를 커스텀 프로퍼티 파일로 저장한다.
        data.write(toFile: plist, atomically: true)
        
        print("custom plist = \(plist)")
    }

3. 저장된 커스텀프로퍼티파일 꺼내 화면에 표시하기

신규계정등록과 동시에 계정을 선택한 것으로 간주하고, selectedAccount키로 저장됨  / 그것을 꺼냄

피커뷰의 Done버튼을 눌렀을때 불러옴 

    // 피커뷰 닫기버튼
    @objc func pickerDone(_ sender: Any){
        self.view.endEditing(true)
        
        // 사용자가 계정을 변경했을때 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
        //선택된 계정에 대한 커스텀 프로퍼티 파일을 읽어와 세팅한다.
        if let _account = self.account.text {
            let customPlist = "\(_account).plist" //읽어올 파일명
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            let path = paths[0] as NSString
            let clist = path.strings(byAppendingPaths: [customPlist]).first!
            let data = NSDictionary(contentsOfFile: clist)
            
            self.name.text = data?["name"] as? String
            self.gender.selectedSegmentIndex = data?["gender"] as? Int ?? 0
            self.married.isOn = data?["married"] as? Bool ?? false
        }
    }

계정정보 미등록시 사용자 정보 입력막기

    // 사용자의 계정의 값이 비어 있다면 값을 설정하는 것을 막는다.************************************************
        if(self.account.text?.isEmpty)!{
            self.account.placeholder = "등록된 계정이 없습니다."
            self.gender.isEnabled = false
            self.married.isEnabled = false
        }

이름 비어있을때는 실행되지 않게

 //테이블뷰에 대한 델리게이트 메소드를 추가.
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        if indexPath.row == 1 && !(self.account.text?.isEmpty)!{ //두번째 셀이 클릭되었을때

사용자가 계정을 새로등록하면 비활성화되어 있던 컨트롤을 활성화 시켜 주어야한다.

그래야 성별과 결혼여부 변경할수 있다.

 // 계정추가버튼
    @objc func newAccount(_ sender: Any){
        // 일단 열려있는 입력용 뷰부터 닫아준다.
        self.view.endEditing(true)
        
        //알림창 객체 생성
        let alert = UIAlertController(title: "새 계정을 입력하세요", message: nil, preferredStyle: .alert)
        //입력폼 추가
        alert.addTextField(){
            $0.placeholder = "ex) abc@gmail.com"
        }
        //버튼 및 액션 정의
        alert.addAction(UIAlertAction(title: "OK", style: .default){ (_) in
            if let account = alert.textFields?[0].text {
                
                self.gender.isEnabled = true
                self.married.isEnabled = true

우측 네비바에 바버튼 추가 / 계정추가버튼추가후 코드로 알림창 띄우기

//네비게이션 바의 오른쪽에 바버튼을 추가하고 클릭시 new Account메소드 실행
let addBtn = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(newAccount(_:)))
self.navigationItem.rightBarButtonItems = [addBtn]

 

 

전체코드

import UIKit

class ListViewController :UITableViewController ,UIPickerViewDelegate,UIPickerViewDataSource{
    
    @IBOutlet weak var account: UITextField!
    
    // 계정목록을 담을 배열정의
    var accountlist = [String]()
    
    @IBOutlet weak var name: UILabel! // 이름
    @IBOutlet weak var gender: UISegmentedControl!//성별
    @IBOutlet weak var married: UISwitch!//결혼여부
    
    //피커뷰ㅍ
    override func viewDidLoad() {
        
        //기본 저장소 객체 불러오기
        let plist = UserDefaults.standard
        
        //불러온 값을 설정
        self.name.text = plist.string(forKey: "name")//이름
        self.married.isOn = plist.bool(forKey: "married")//성별
        self.gender.selectedSegmentIndex = plist.integer(forKey: "gender")//결혼여부
        
        let picker = UIPickerView()
        //피커뷰의 델리게이트 객체 지정
        picker.delegate = self
        //account 텍스트필드 입력 방식을 가상 키보드 대신 피커뷰로 설정
        self.account.inputView = picker
        
        //툴바 객체 정의
        let toolbar = UIToolbar()
        toolbar.frame = CGRect(x: 0, y: 0, width: 0, height: 35)
        toolbar.barTintColor = .lightGray
        //액세서리 뷰 영역에 툴바를 표시
        self.account.inputAccessoryView = toolbar
        //툴바에 들어갈 닫기 버튼
        let done = UIBarButtonItem()
        done.title = "Done"
        done.target = self
        done.action = #selector(pickerDone)// 닫기메소드
        
        //신규계정등록버튼
        let new = UIBarButtonItem()
        new.title = "New"
        new.target = self
        new.action = #selector(newAccount(_:))// 계정추가메소드
        // 가변 폭 버튼 정의
        let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        
        //버튼을 모두 툴바에 추가 뉴버튼 / 사이공간 / 닫기버튼
        toolbar.setItems([new,flexSpace,done], animated: true)
        
        
        // 사용자의 계정의 값이 비어 있다면 값을 설정하는 것을 막는다.************************************************
        if(self.account.text?.isEmpty)!{
            self.account.placeholder = "등록된 계정이 없습니다."
            self.gender.isEnabled = false
            self.married.isEnabled = false
        }
        
        // 계정정보 읽어오기 /////////////////////////////////////////////////////////////////////////////
        // accountlist키 로 저장된 값을 읽어온다. 저장된 값이 없을 경우 새로운 배열객체를 생성한다.
        let accountlist = plist.array(forKey: "accountlist") as? [String] ?? [String]()
        // 읽어온값을 멤버변수에 대입한다.
        self.accountlist = accountlist
        //1. selectedAccount키에 저장된 값이 있으면, 이를 사용하여 읽어와야할 파일의 이름을 구성한다.
        if let account = plist.string(forKey: "selectedAccount"){
            // 값이 있을경우에 account의 텍스트 필드 값으로 대입한다.
            self.account.text = account
        
            // 저장된 프로퍼티파일 가져오기 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
            let customPlist = "\(account).plist" //읽어올 파일명
            //2. 앱내에 정의된 문서 디렉토리 경로를 가져온 다음 파일명을 조합하여 전체 경로를 구성한다.
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            let path = paths[0] as NSString
            let clist = path.strings(byAppendingPaths: [customPlist]).first!
            // 3.저장된파일을 읽어와서 딕셔너리 객체로 전환한다.
            let data = NSDictionary(contentsOfFile: clist)
            // name키에 저장된 값을 꺼내어 이름 레이블에 세팅한다.
            self.name.text = data?["name"] as? String
            // 젠더 키에 저장된 값을 꺼내여 세그먼트 컨트롤에 세팅한다. 만약 값이 없다면 0으로 설정한다.
            self.gender.selectedSegmentIndex = data?["gender"] as? Int ?? 0
            //married키에 저장된 값을 꺼내어 스위치 컨트롤에 세팅한다. 만약 값이 없다면 false로 설정
            self.married.isOn = data?["married"] as? Bool ?? false
        }
        
        //네비게이션 바의 오른쪽에 바버튼을 추가하고 클릭시 new Account메소드 실행
        let addBtn = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(newAccount(_:)))
        self.navigationItem.rightBarButtonItems = [addBtn]
      
    }
    
    //생성할 컴포턴트의 갯수를 정의합니다.
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
        
    }
    //지정된 컴포턴트가 가질 목록의 길이를 정의합니다.
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return self.accountlist.count
    }
    // 지정된 컴포넌트의 목록 각 행에 출력될 내용을 정의합니다.행번호에 맞게출력된다.
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return self.accountlist[row]
    }
    // 지정된 컴포넌트의 목록 각 행을 사용자가 선택했을 때 실행할 액션을 정의합니다.
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        // 선택된 계정값을 텍스트 필드에 입력
        let account = self.accountlist[row]
        self.account.text = account
        
        //사용자가 계정을 생성하면 이 계정을 선택한 것을 간주하고 저장한다.@@@@@@@@@@@@@@@@@@@@@@@@@
        let plist = UserDefaults.standard
        plist.set(account, forKey: "selectedAccount")
        plist.synchronize()
        
        // 입력 뷰를 닫음
        // self.view.endEditing(true)
    }
    
    // 피커뷰 닫기버튼
    @objc func pickerDone(_ sender: Any){
        self.view.endEditing(true)
        
        // 사용자가 계정을 변경했을때 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
        //선택된 계정에 대한 커스텀 프로퍼티 파일을 읽어와 세팅한다.
        if let _account = self.account.text {
            let customPlist = "\(_account).plist" //읽어올 파일명
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            let path = paths[0] as NSString
            let clist = path.strings(byAppendingPaths: [customPlist]).first!
            let data = NSDictionary(contentsOfFile: clist)
            
            self.name.text = data?["name"] as? String
            self.gender.selectedSegmentIndex = data?["gender"] as? Int ?? 0
            self.married.isOn = data?["married"] as? Bool ?? false
            
        }
    }
    
    // 게정추가버튼
    @objc func newAccount(_ sender: Any){
        // 일단 열려있는 입력용 뷰부터 닫아준다.
        self.view.endEditing(true)
        
        //알림창 객체 생성
        let alert = UIAlertController(title: "새 계정을 입력하세요", message: nil, preferredStyle: .alert)
        //입력폼 추가
        alert.addTextField(){
            $0.placeholder = "ex) abc@gmail.com"
        }
        //버튼 및 액션 정의
        alert.addAction(UIAlertAction(title: "OK", style: .default){ (_) in
            if let account = alert.textFields?[0].text {
                
                self.gender.isEnabled = true
                self.married.isEnabled = true
                
                //계정 목록 배열에 추가한다.
                self.accountlist.append(account)
                //계정 텍스트 필드에 표시한다.
                self.account.text = account
                //컨트롤 값을 모두 초기화한다.
                self.name.text = ""
                self.gender.selectedSegmentIndex = 0
                self.married.isOn = false
                
                // 계정 목록을 통째로 저장한다.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                let plist = UserDefaults.standard
                plist.set(self.accountlist, forKey: "accountlist")
                // 신규로 등록시 계정값을 받아서 selectedAccount키로 저장한다.
                plist.set(account, forKey: "selectedAccount")
                plist.synchronize()
                // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
        
            }
        })
        //알림창 오픈
        self.present(alert, animated: false, completion: nil)
    }
    
    // 성별액션메소드
    @IBAction func changeGender(_ sender: UISegmentedControl) {
        let value = sender.selectedSegmentIndex //0이면 남자, 1이면 여자
        
//        let plist = UserDefaults.standard // 기본객체저장소를 가져옴
//        plist.set(value, forKey: "gender")//젠더라는키로 가져옴
//        plist.synchronize()// 동기화
        
        // 커스텀프로퍼티에 저장$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
        let customPlist = "\(self.account.text!).plist" // 읽어올파일명
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let path = paths[0] as NSString
        let plist = path.strings(byAppendingPaths: [customPlist]).first!
        //읽어온 파일을 딕셔너리 객체로 변환한다. 만약 해당위치에 파일이 없다면 새로운 딕셔너리 객체를 생성한다.
        let data = NSMutableDictionary(contentsOfFile: plist) ?? NSMutableDictionary()
        // 입력된 이름값을 딕셔너리 객체에 "name"키로 저장한다.
        data.setValue(value, forKey: "gender")
        // 딕셔너리객체를 커스텀 프로퍼티 파일로 저장한다.
        data.write(toFile: plist, atomically: true)
    }
    
    // 결혼여부액션메소드
    @IBAction func changeMarried(_ sender: UISwitch) {
        let value = sender.isOn //트루면 기혼
        
//        let plist = UserDefaults.standard
//        plist.set(value, forKey: "married")
//        plist.synchronize()
        
        // 커스텀프로퍼티에 저장$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
        let customPlist = "\(self.account.text!).plist" // 읽어올파일명
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let path = paths[0] as NSString
        let plist = path.strings(byAppendingPaths: [customPlist]).first!
        //읽어온 파일을 딕셔너리 객체로 변환한다. 만약 해당위치에 파일이 없다면 새로운 딕셔너리 객체를 생성한다.
        let data = NSMutableDictionary(contentsOfFile: plist) ?? NSMutableDictionary()
        // 입력된 이름값을 딕셔너리 객체에 "name"키로 저장한다.
        data.setValue(value, forKey: "married")
        // 딕셔너리객체를 커스텀 프로퍼티 파일로 저장한다.
        data.write(toFile: plist, atomically: true)
        
        print("custom plist = \(plist)")
    }
    
    //테이블뷰에 대한 델리게이트 메소드를 추가.
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        if indexPath.row == 1 && !(self.account.text?.isEmpty)!{ //두번째 셀이 클릭되었을때
            //입력이 가능한 알림창을 띄워 이름을 수정할수 있게한다.
            let alert = UIAlertController(title: nil, message: "이름을 입력하세요", preferredStyle: .alert)
            //입력필드 추가
            alert.addTextField(){
                $0.text = self.name.text // 이름레이블의 텍스트 넣어주기
            }
            
            // 버튼 및 액션 추가
            alert.addAction(UIAlertAction(title: "OK", style: .default){(_) in
                
                //사용자가 OK버튼을 누르면 입력 필드에 입력된 값을 저장한다.
                let value = alert.textFields?[0].text
                
//                let plist = UserDefaults.standard
//                plist.setValue(value,forKey: "name")
//                plist.synchronize()
                
                // 커스텀프로퍼티에 저장$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                // 현재입력된 계정을 바탕으로 읽어올 커스텀 프로퍼티 파일명을 정의한다.
                let customPlist = "\(self.account.text!).plist" // 읽어올 파일명
                // 앱 내에 생성된 문서 디렉터리 경로를 구한다.
                let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
                let path = paths[0] as NSString
                let plist = path.strings(byAppendingPaths: [customPlist]).first!
                //읽어온 파일을 딕셔너리 객체로 변환한다. 만약 해당위치에 파일이 없다면 새로운 딕셔너리 객체를 생성한다.
                let data = NSMutableDictionary(contentsOfFile: plist) ?? NSMutableDictionary()
                // 입력된 이름값을 딕셔너리 객체에 "name"키로 저장한다.
                data.setValue(value, forKey: "name")
                // 딕셔너리객체를 커스텀 프로퍼티 파일로 저장한다.
                data.write(toFile: plist, atomically: true)
                
                self.name.text = value
            })
            //알림창띄움
            self.present(alert, animated: false, completion: nil)
        }
    }
    
}
반응형
Comments