EEYatHo 앱 깎는 이야기

Swift ) xlsx 이미지까지 자유롭게 - EEYatHo iOS 본문

iOS, Swift/Feature

Swift ) xlsx 이미지까지 자유롭게 - EEYatHo iOS

EEYatHo 2022. 2. 21. 00:12
반응형

 

만약 string만 다루는 파일이라면, 그냥 csv로 하면 간단하고 가볍다.

하지만 난 이미지를 다뤄야 하는 상황이다..

csv 다루기

 

 

image -> data -> string으로 바꾸는 헛짓거리를 하지않고,

서버에서 내려받는 image url 형식으로도 하지않고,

그냥 엑셀답게!! 셀안에 이미지를 넣는게 목적.

 

때문에 image를 다룰 수 있냐?를 중점으로 xlsx 라이브러리를 찾아봤다.

 

 

 

제공되고 있는 xlsx 관련 라이브러리들


 

 

각 라이브러리 특징


1. libxlsxwriter 

  • string 읽기 불가, 쓰기 가능
  • image 읽기 불가, 쓰기 가능
  • 엑셀(.xlsx) 파일 생성 가능

링크 누르면 나오는 페이지에서, 중간쯤에 Installation on macOS and iOS 부분을 보면 cocoapod 설치가능

 

 

 

2. XlsxReaderWriter

 

  • string 읽기, 쓰기 가능
  • image 읽기 가능, image 쓰기 불가 ( 지원한다고 되어있는데 작동하지 않음 ㅠ 이슈도 나와있는데 몇년째 미해결 )
  • 엑셀(.xlsx) 파일 생성 불가, 꼼수를 사용하면 가능 ( 이미 있는 .xlsx파일을 열어서 수정하고 다른이름으로 저장하건 가능하기에, 빈 엑셀파일을 미리 프로젝트에 넣어두고 사용하면 기능상 불가능하진 않다 )

image 쓰기만 가능했으면, 기능상으로는 모두 상위호환이었는데 아깝다..

 

 

 

3. CoreXLSX

  • string 읽기, 쓰기 가능
  • image 자체를 지원안함 필요없어서 이 이상 안알아보았다.

 

 

Solution


엑셀파일 생성할 때는 xlsxwriter로, 

읽을 때는 XlsxReaderWriter로 구현하니 잘되었다.

 

1. xlsxwriter 사용한 write 코드

func writeXlsx(fileName: String, maxImageLength: CGFloat = 1024.0) {

    // 폴더 생성
    func _makeFolder() -> String? {
        let fileManager = FileManager.default
        let documentUrl = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
        let directoryUrl = documentUrl.appendingPathComponent("NemoXlsxFolder")

        do {
            try fileManager.createDirectory(atPath: directoryUrl.path, withIntermediateDirectories: true, attributes: nil)
            return directoryUrl.path
        }
        catch let error as NSError {
            print("Ooops! Something went wrong: \(error)")
            return nil
        }
    }

    guard let foldPath = _makeFolder() else {
        return
    }

    // 포인터들 생성
    var workbook: UnsafeMutablePointer<lxw_workbook>? = nil
    var sheet: UnsafeMutablePointer<lxw_worksheet>? = nil
    func _makeXlsxPointer() {
        let xlsxPath = "\(foldPath)/\(fileName).xlsx"
        // 엑셀 파일 포인터 생성
        workbook = workbook_new(xlsxPath)
        // 시트 포인터 생성
        sheet = workbook_add_worksheet(workbook, "Contents")
    }
    _makeXlsxPointer()


    // 헤더(제일 윗 행) 설정
    func _setXlsxHeader() {
        let format = workbook_add_format(workbook)
        format_set_bold(format) // 볼드 처리

        // 제일 윗 줄 문자열 세팅
        worksheet_write_string(sheet, 0, 0, "Animal", format)
    }
    _setXlsxHeader()


    // 내용 채우기
    func _setContents() {
        
        let string = "cat"
        let image = UIImage(named: "cat")
        
        // 문자열 쓰기
        worksheet_write_string(sheet, 1, 0, string, nil)

        // 이미지 쓰기
        var options = lxw_image_options()
        if let image = image {

            let imageScale = image.scale
            let uiimageSizeInPixel = (Double(image.size.width * imageScale), Double(image.size.height * imageScale))

            let bigger = uiimageSizeInPixel.0 > uiimageSizeInPixel.1 ? uiimageSizeInPixel.0 : uiimageSizeInPixel.1

            var scale = 1.0
            if bigger > maxImageLength {
                scale = maxImageLength / bigger
            }

            options.x_offset = 1
            options.y_offset = 1
            options.x_scale = scale
            options.y_scale = scale
            options.object_position = 1

            let buffer = getArrayOfBytesFromImage(imageData: image as NSData)
            worksheet_insert_image_buffer_opt(sheet, 1, lxw_col_t(1), buffer, buffer.count, &options)
        }

    }
    _setContents()

    print("Writing Xlsx to: \(foldPath)/\(fileName)")
    
    // 저장
    workbook_close(workbook)
}

/// Update NSData to buffer for xlsxwriter
private func getArrayOfBytesFromImage(imageData: NSData) -> [UInt8] {
   //Determine array size
   let count = imageData.length / MemoryLayout.size(ofValue: UInt8())
   //Create an array of the appropriate size
   var bytes = [UInt8](repeating: 0, count: count)
   //Copy image data as bytes into the array
   imageData.getBytes(&bytes, length:count * MemoryLayout.size(ofValue: UInt8()))

   return bytes
}

 

2. XlsxReaderWriter 사용한 read 코드

iPhone에서 .xlsx 파일을 선택하기 위해서는 UIDocumentPickerDelegate를 사용하면 된다

@objc func readXlsxButtonClick() {
    var vc: UIDocumentPickerViewController? = nil
    if #available(iOS 14.0, *) {
        vc = UIDocumentPickerViewController(forOpeningContentTypes: [.compositeContent], asCopy: true)
    } else {
        vc = UIDocumentPickerViewController(documentTypes: ["org.openxmlformats.spreadsheetml.sheet"], in: .import)
    }
    if let vc = vc {
        vc.delegate = self
        vc.allowsMultipleSelection = false
        vc.modalPresentationStyle = .fullScreen
        present(vc, animated: true)
    }
}

// UIDocumentPickerDelegate
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    print(urls)
    if let url = urls.first {
        XlsxManager.shared.readXlsxUseXlsxReaderWriter(url: url)
    }
}

// XlsxReaderWriter
func readXlsx(url: URL) {
    let documentPath: String = url.path
    let xlsx: BRAOfficeDocumentPackage = BRAOfficeDocumentPackage.open(documentPath)

    if let sheet = xlsx.workbook.worksheets[0] as? BRAWorksheet {
        
        let cellRef = "A2"
        if let cell = sheet.cell(forCellReference: cellRef) {

            if cell.hasError {
                print("cell error: \(cell.stringValue() ?? "")")
            }


            if let str = cell.stringValue() {
                print("cell str = \(str)")
            }

            if let formula = cell.formulaString() {
                print("cell attr = \(formula)")
            }
            if let attr = cell.attributedStringValue() {
                print("cell attr = \(attr)")
            }

            print("cell bool = \(cell.boolValue())")

            print("cell int = \(cell.integerValue())")

            print("cell float = \(cell.floatValue())")

            if let attr = cell.attributedStringValue() {
                print("cell attr = \(attr)")
            }
        }

        if let image = sheet.image(forCellReference: cellRef)?.uiImage {
            print("cell image = \(image)")
        }
    }
}

 

 

 

버그


추가로, xlsxwriter, XlsxReaderWriter 같이 설치하니까 아래 2개가 충돌이 난다.

  • Pods/libxlsxwriter/zip.h 파일에서 zip_fileinfo 구조체안에 tmz_date
  • Pods/XlsxReaderWriter/mz_compat.h 파일에서 zip_fileinfo 구조체안에 tmz_date

 

-> XlsxReaderWriter 프레임워크 안에 있는 모든 tmz_date변수들 이름을 tmz_date2로 바꿔서 해결.

-> ? 포스팅할 때 다시 tmz_date로 되돌리니까 에러가 발생하지 않음.. 

Comments