본문 바로가기
아이폰 개발/Swift

swift fmdb 예제 1 - feat: tableview , typealias , 튜플, do catch

by 인생여희 2021. 3. 30.

swift fmdb 예제 1 - feat: tableview , typealias , 튜플, do catch

 

준비

먼저 fmdb 라이브러리를 가져온다.

xcode 에서 source control -> clone...  클릭 후 , fmdb git 주소를 적어주고 프로젝트를 복사한다음, fmdb 파일만 프로젝트로 가져온다.

 

 

그리고 위의 사진처럼 브릿징 파일을 만들어주고, 아래 그림처럼 build settings 에서 브릿징 파일을 연결해 준다. 

 

 

 

 

 

스토리보드

 

 

부서관련 DAO

 

1.DepartRecord 튜플 선언

2.FMDatabase 변수 선언

3.init 

4.부서 목록 읽어오기

5.단일 부서 읽어오기

6.부서정보 추가

7.부서정보 삭제

 

import Foundation

class DepartmentDAO {
  
    //MARK: DepartRecord 튜플
    //부서 정보를 담을 튜플 정의 : 부서코드, 부서명, 부서주소
    //전역 범위에서 선언한 typealias는 프로젝트 전체에서 사용 가능한 일반 자료형처럼 취급됩니다.
    typealias DepartRecord = (Int , String, String)
    
/*
     fmdb는 지연 저장 프로퍼티로 정의되어 있기 때문에
     실제로 참조 되기 전까지 초기화되지 않습니다.
     지연 저장 프로퍼티 내의 클로저 구문도 초기화되는 시점에 단 한 번만 실행되며 이후에는 처음 초기화된 값을 그대로 사용합니다.
*/
    //MARK: FMDatabase
    lazy var fmdb:FMDatabase! = {
        
        //파일 매니저 객체를 생성
        let fileMgr = FileManager.default
        
        //샌드박스 내 문서 디렉터리에서 데이터베이스 파일 경로 확인
        let docPath = fileMgr.urls(for: .documentDirectory, in: .userDomainMask).first
        let dbPath = docPath!.appendingPathComponent("db.sqlite").path
        
        print("fmdb dbPath : \(dbPath)")
        
        // 샌드박스 경로에 데이터베이스 파일이 없다면 메인 번들의 데이터베이스 파일을 가져와서 복사
        if fileMgr.fileExists(atPath: dbPath) == false {
            
            let dbSource = Bundle.main.path(forResource: "db", ofType: "sqlite")
            
            try! fileMgr.copyItem(atPath: dbSource!, toPath: dbPath)
        }
        
        //준비된 데이터 베이스 파일을 바탕으로 FMdatabase 객체 생성
    /*
            데이터베이스 경로는 3가지의 값을 사용할 수 있습니다.
            1. 파일 시스템 경로 - 시스템상의 데이터베이스 파일 위치. 파일이 없을 경우 생성됨
            2. 빈 문자열("") - 데이터베이스가 임시 저장 경로에 생성되고 FMDatabase 객체의 연결이 해제되면 동시에 제거됨. 임시 저장 공간이 필요하거나 잠깐 동안 데이터베이스가 필요할 때 사용
            3. nil - 임시 데이터베이스가 메모리에 생성됨. FMDatabase 객체의 연결이 해제되면 동시에 제거됨.
        */
        let db = FMDatabase(path: dbPath)
        
        return db
    }()
    
    //MARK: init
    init() {
        //데이터베이스 연결
        //open() 메서드가 성공하면 true, 실패하면 false를 반환하므로 그에 따른 분기문을 작성할 수도 있습니다.
        self.fmdb.open()
    }
    
    
    deinit {
        //데이터 베이스 연결종료
        self.fmdb.close()
    }
    
    
    //MARK: 부서목록 읽어오기
    func find() -> [DepartRecord] {
        //반환할 데이터를 담을 [DepartRecord] 타입 객체 정의
        var departList = [DepartRecord]()
        
        do {
            
            //부서 정보 목록을 가져올 sql 쿼리
            let sql = """
                    SELECT depart_cd , depart_title , depart_addr
                    FROM department
                    ORDER BY depart_cd ASC
                    """
            
            //FMResultSet 객체는 레코드 구조로 되어있으며 테이블의 각 행과 같습니다.
            //next() 메서드를 이용해서 순서대로 다음 레코드로 옮길 수 있습니다.
            let rs = try self.fmdb.executeQuery(sql, values: nil)
        
        
            //결과 집합 추출
            while rs.next() {
                
                let departCd = rs.int(forColumn: "depart_cd")
                let departTitle = rs.string(forColumn: "depart_title")
                let departAddr = rs.string(forColumn: "depart_addr")
                
                //append 메소드 호출 시 괄호로 감싸줘야함 (하나의튜플)
                departList.append((Int(departCd) , departTitle! , departAddr!))
            }
        
        
        } catch let error as NSError {
            print("failed: \(error.localizedDescription)")
        }
     
        return departList
    }
    
    //MARK: 단일 부서 읽어오기
    func get(departCd:Int) -> DepartRecord?{
        

        do {
            //sql 쿼리
            let sql = """
                                    SELECT depart_cd , depart_title , depart_addr
                                    FROM department
                                    WHERE depart_cd = ?
                                """
            
            let rs = try self.fmdb.executeQuery(sql, values: [departCd])
            
            //결과집합 처리
            
            rs.next()
            let departId = rs.int(forColumn: "depart_cd")
            let departTitle = rs.string(forColumn: "depart_title")
            let departAddr = rs.string(forColumn: "depart_addr")
                
            return (Int(departId) , departTitle!, departAddr!)

        } catch let error as NSError {
            print("failed : \(error.localizedDescription)")
            return nil
        }
        
    }
    
    
    //MARK: 부서정보 추가
    func create(title:String! , addr:String!) -> Bool {
        
        //nil 체크
        guard title != nil && title.isEmpty == false else {
            return false
        }
        
        guard addr != nil && addr.isEmpty == false else {
            return false
        }
        
        
        do {
            
            let sql = """
                                  INSERT INTO department(depart_title, depart_addr)
                                  VALUES (? ,?)
                                  """
            
            //생성
            try self.fmdb.executeUpdate(sql, values: [title!, addr!])
            return true
        
        } catch let error as NSError {
            
            print("Insert Error: \(error.localizedDescription)")
            return false
            
        }
        
    }
    
    
    //MARK: 부서정보 삭제
    func remove(departCd:Int) -> Bool {
        
        do {
            let sql = "DELETE FROM department WHERE depart_cd = ?"
            
            try self.fmdb.executeUpdate(sql, values: [departCd])
            return true
            
        } catch let error as NSError {
            
            print("DELETE Error: \(error.localizedDescription)")
            return false
        
        }
    }
    
    
    
}

 

 

부서 목록 테이블 뷰 로직



import UIKit

class DepartmentListVC: UIViewController , UITableViewDelegate, UITableViewDataSource{

    

    @IBOutlet weak var tableView: UITableView!
    
    //테이블 뷰의 데이터 소스를 담당할 배열
    var departList : [(departCd:Int , departTitle:String , departAddr: String)]!
    
    let departDAO = DepartmentDAO() //SQLite DAO 객체
    
    //전체행 삭제
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        if editing {
            print("edit Mode on")
            self.tableView.setEditing(true, animated: true)
        } else {
            print("Done leave editmode")
            self.tableView.setEditing(false, animated: true)
        }
    }
    
    //ui 생성
    func initUI(){
        
        //내비게이션 타이틀용 레이블 속성 설정
        let navTitle = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
        navTitle.numberOfLines = 2
        navTitle.textAlignment = .center
        navTitle.font = UIFont.systemFont(ofSize: 14)
        navTitle.text = "부서목록 \n" + " 총 \(self.departList.count) 개"
        
        
        //네비게이션 바 UI
        self.navigationItem.titleView = navTitle
        self.navigationItem.leftBarButtonItem = self.editButtonItem //편집 버튼 추가
        
        
        //셀을 스와이프 했을 때 , 해당 셀만 편집 모드가 되도록 설정
        self.tableView.allowsSelectionDuringEditing = true
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        self.tableView.delegate = self
        self.tableView.dataSource = self
        
        //기존 저장된 부서정보 가져옴
        self.departList = self.departDAO.find()
        
        self.initUI()
        
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return self.departList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let rowData = self.departList[indexPath.row]
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "depart_cell")
        
        cell?.textLabel?.text = rowData.departTitle
        cell?.textLabel?.font = UIFont.systemFont(ofSize: 14)
        cell?.detailTextLabel?.text = rowData.departAddr
        cell?.detailTextLabel?.font = UIFont.systemFont(ofSize: 12)
        
        return cell!
        
    }

    
    //MARK: - 부서 등록
    @IBAction func add(_ sender: Any) {
        
        let alert = UIAlertController(title: "부서등록", message: "신규부서를 등록해주세요", preferredStyle: .alert)
        
        alert.addTextField { (tf) in
            tf.placeholder = "부서명"
        }
        alert.addTextField { (tf) in
            tf.placeholder = "주소"
        }
        
        
        alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil))
        
        alert.addAction(UIAlertAction(title: "확인", style: .default, handler: { (_) in
            
            
            let title = alert.textFields?[0].text
            let addr = alert.textFields?[1].text
            
            if self.departDAO.create(title: title!, addr: addr!) {
                //신규 부서 등록 후 DB에서 목록 다시 읽고 테이블 뷰 갱신
                self.departList = self.departDAO.find()
                self.tableView.reloadData()
                
                //네비게이션 타이틀도 변경된 부서 정보 반영
                let navTitle = self.navigationItem.titleView as! UILabel
                navTitle.text = "부서목록 \n" + " 총 \(self.departList.count) 개"
            }
            
        }))
        
        self.present(alert, animated: false, completion: nil)

    }
    
    
    //MARK: - 편집
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        
        //삭제할 행의 departCd를 구함
        let departCd = self.departList[indexPath.row].departCd
        
        //DB , 데이터 소스, 테이블 뷰에서 삭제
        if departDAO.remove(departCd: departCd) {
            self.departList.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
     
    }
    
    
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        return .delete
    }
    
    
}

 

 

FMDB_TEST 2.zip
1.85MB

 

참고

https://blog.naver.com/go4693/221389897449