투케이2K

128. (ios/swift) photoLibrary 사진 파일 선택 및 멀티 파트 폼 데이터 (multipart form data) 사용해 서버에 업로드 실시 본문

IOS

128. (ios/swift) photoLibrary 사진 파일 선택 및 멀티 파트 폼 데이터 (multipart form data) 사용해 서버에 업로드 실시

투케이2K 2022. 3. 15. 12:52
반응형

[개발 환경 설정]

개발 툴 : XCODE

개발 언어 : SWIFT

 

[사전 설정]

 

[소스 코드]

import UIKit

// MARK: [import 추가 실시]
import AVFoundation
import Photos



class A_Image: UIViewController {
    
    
    
    // MARK: [클래스 설명]
    /*
    1. A_Image : 카메라, 앨범 접근 클래스
    2. info.plist 권한 등록 필요 :
       - Privacy - Camera Usage Description
       - Privacy - Photo Library Usage Description
       - Privacy - Access to a File Provide Domain Usage Description
       - Privacy - Microphone Usage Description
    */
    
    
    
    // MARK: - [전역 변수 선언 실시]
    @IBOutlet weak var imageView: UIImageView! // 이미지 뷰
    let photo = UIImagePickerController() // 앨범 이동을 위한 컨트롤러
    var imageData : NSData? = nil // 서버로 이미지 등록을 하기 위함
    
    
    
    
    // MARK: - [뷰 로드 실시]
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("")
        print("===============================")
        print("[A_Image >> viewDidLoad() :: 뷰 로드 실시]")
        print("===============================")
        print("")
        
        
        // [초기 이미지 뷰 배경 색상 지정 실시]
        self.imageView.backgroundColor = UIColor.gray
        
        
        // MARK: [앨범 컨트롤러 딜리게이트 지정 실시]
        self.photo.delegate = self
    }
    
    
    
    // MARK: - [뷰 로드 완료]
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("")
        print("===============================")
        print("[A_Image >> viewWillAppear() :: 뷰 로드 완료]")
        print("===============================")
        print("")
    }
        
    
    
    // MARK: - [뷰 화면 표시]
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print("")
        print("===============================")
        print("[A_Image >> viewDidAppear() :: 뷰 화면 표시]")
        print("===============================")
        print("")
        // -----------------------------------------
        // [뷰 컨트롤러 포그라운드, 백그라운드 상태 체크 설정 실시]
        NotificationCenter.default.addObserver(self, selector: #selector(checkForeground), name: UIApplication.willEnterForegroundNotification, object: nil) // [포그라운드]
        NotificationCenter.default.addObserver(self, selector: #selector(checkBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) // [백그라운드]
        // -----------------------------------------
        // [포그라운드 처리 실시]
        checkForeground()
        // -----------------------------------------
    }
        
    
    
    // MARK: - [뷰 정지 상태]
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print("")
        print("===============================")
        print("[A_Image >> viewWillDisappear() :: 뷰 정지 상태]")
        print("===============================")
        print("")
    }
        
    
    
    // MARK: - [뷰 종료 상태]
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        print("")
        print("===============================")
        print("[A_Image >> viewDidDisappear() :: 뷰 종료 상태]")
        print("===============================")
        print("")
        // -----------------------------------------
        // [뷰 컨트롤러 포그라운드, 백그라운드 상태 체크 해제 실시]
        NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) // [포그라운드]
        NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) // [백그라운드]
        // -----------------------------------------
    }
    
    
    
    // MARK: - [포그라운드 및 백그라운드 상태 처리 메소드 작성]
    @objc func checkForeground() {
        print("")
        print("===============================")
        print("[A_Image >> checkForeground() :: 뷰 컨트롤러 포그라운드]")
        print("===============================")
        print("")
    }
    @objc func checkBackground() {
        print("")
        print("===============================")
        print("[A_Image >> checkBackground() :: 뷰 컨트롤러 백그라운드]")
        print("===============================")
        print("")
    }
    
    
    
    // MARK: - [버튼 클릭 이벤트 처리 실시]
    @IBAction func buttonAction(_ sender: Any) {
        print("")
        print("===============================")
        print("[A_Image >> buttonAction() :: 앨범 열기 버튼 클릭 이벤트 발생]")
        print("===============================")
        print("")
        
        
        // [앨범의 사진에 대한 접근 권한 확인 실시]
        PHPhotoLibrary.requestAuthorization( { status in
            switch status{
            case .authorized:
                print("")
                print("===============================")
                print("[A_Image >> buttonAction() :: 앨범의 사진에 대한 접근 권한 확인 실시]")
                print("상태 :: 앨범 권한 허용")
                print("===============================")
                print("")
                
                // [앨범 열기 수행 실시]
                self.openPhoto()
                break
                
            case .denied:
                print("")
                print("===============================")
                print("[A_Image >> buttonAction() :: 앨범의 사진에 대한 접근 권한 확인 실시]")
                print("상태 :: 앨범 권한 거부")
                print("===============================")
                print("")
                break
                
            case .notDetermined:
                print("")
                print("===============================")
                print("[A_Image >> buttonAction() :: 앨범의 사진에 대한 접근 권한 확인 실시]")
                print("상태 :: 앨범 권한 선택하지 않음")
                print("===============================")
                print("")
                break
                
            case .restricted:
                print("")
                print("===============================")
                print("[A_Image >> buttonAction() :: 앨범의 사진에 대한 접근 권한 확인 실시]")
                print("상태 :: 앨범 접근 불가능, 권한 변경이 불가능")
                print("===============================")
                print("")
                break
                
            default:
                break
            }
        })
    }
    
    
    
    // MARK: - [앨범 열기 수행 실시]
    func openPhoto(){
        DispatchQueue.main.async {
            print("")
            print("===============================")
            print("[A_Image >> openPhoto() :: 앨범 열기 수행 실시]")
            print("===============================")
            print("")
            // -----------------------------------------
            // [사진 찍기 카메라 호출]
            
            self.photo.sourceType = .photoLibrary // 앨범 지정 실시
            self.photo.allowsEditing = false // 편집을 허용하지 않음
            self.present(self.photo, animated: false, completion: nil)
            // -----------------------------------------
        }
    }
    
    
    
    // MARK: - [URL Session Post 멀티 파트 사진 데이터 업로드]
    func requestPOST() {
        
        // MARK: [URL 지정 실시]
        let urlComponents = URLComponents(string: "https://app.test.ac.kr/upload_image")
        
        
        
        // MARK: [사진 파일 파라미터 이름 정의 실시]
        let file = "file"
        
        
        
        // MARK: [전송할 데이터 파라미터 정의 실시]
        var reqestParam : Dictionary<String, Any> = [String : Any]()
        reqestParam["idx"] = 201 // 일반 파라미터
        reqestParam["\(file)"] = self.imageData! as NSData // 사진 파일
        
        
        
        // [boundary 설정 : 바운더리 라인 구분 필요 위함]
        let boundary = "Boundary-\(UUID().uuidString)" // 고유값 지정
        
        print("")
        print("====================================")
        print("[A_Image >> requestPOST() :: 바운더리 라인 구분 확인 실시]")
        print("boundary :: ", boundary)
        print("====================================")
        print("")
        
        
        
        // [http 통신 타입 및 헤더 지정 실시]
        var requestURL = URLRequest(url: (urlComponents?.url)!) // url 주소 지정
        requestURL.httpMethod = "POST" // POST 방식
        requestURL.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") // 멀티 파트 타입
        
        
        
        // [서버로 전송할 uploadData 데이터 형식 설정]
        var uploadData = Data()
        let boundaryPrefix = "--\(boundary)\r\n"
        
        
        
        // [멀티 파트 전송 파라미터 삽입 : 딕셔너리 for 문 수행]
        for (key, value) in reqestParam {
            if "\(key)" == "\(file)" { // MARK: [사진 파일 인 경우]
                print("")
                print("====================================")
                print("[A_Image >> requestPOST() :: 멀티 파트 전송 파라미터 확인 실시]")
                print("타입 :: ", "사진 파일")
                print("key :: ", key)
                print("value :: ", value)
                print("====================================")
                print("")
                
                uploadData.append(boundaryPrefix.data(using: .utf8)!)
                uploadData.append("Content-Disposition: form-data; name=\"\(file)\"; filename=\"\(file)\"\r\n".data(using: .utf8)!) // [파라미터 key 지정]
                uploadData.append("Content-Type: \("image/jpg")\r\n\r\n".data(using: .utf8)!) // [전체 이미지 타입 설정]
                uploadData.append(value as! Data) // [사진 파일 삽입]
                uploadData.append("\r\n".data(using: .utf8)!)
                uploadData.append("--\(boundary)--".data(using: .utf8)!)
            }
            else { // MARK: [일반 파라미터인 경우]
                print("")
                print("====================================")
                print("[A_Image >> requestPOST() :: 멀티 파트 전송 파라미터 확인 실시]")
                print("타입 :: ", "일반 파라미터")
                print("key :: ", key)
                print("value :: ", value)
                print("====================================")
                print("")
                
                uploadData.append(boundaryPrefix.data(using: .utf8)!)
                uploadData.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!) // [파라미터 key 지정]
                uploadData.append("\(value)\r\n".data(using: .utf8)!) // [value 삽입]
            }
        }

        
        
        // [http 요쳥을 위한 URLSessionDataTask 생성]
        print("")
        print("====================================")
        print("[A_Image >> requestPOST() :: 사진 업로드 요청 실시]")
        print("url :: ", requestURL)
        print("uploadData :: ", uploadData)
        print("====================================")
        print("")
        
        // MARK: [URLSession uploadTask 수행 실시]
        let dataTask = URLSession(configuration: .default)
        dataTask.configuration.timeoutIntervalForRequest = TimeInterval(20)
        dataTask.configuration.timeoutIntervalForResource = TimeInterval(20)
        dataTask.uploadTask(with: requestURL, from: uploadData) { (data: Data?, response: URLResponse?, error: Error?) in

            // [error가 존재하면 종료]
            guard error == nil else {
                print("")
                print("====================================")
                print("[A_Image >> requestPOST() :: 사진 업로드 요청 실패]")
                print("fail : ", error?.localizedDescription ?? "")
                print("====================================")
                print("")
                return
            }

            // [status 코드 체크 실시]
            let successsRange = 200..<300
            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, successsRange.contains(statusCode)
            else {
                print("")
                print("====================================")
                print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                print("error : ", (response as? HTTPURLResponse)?.statusCode ?? 0)
                print("allHeaderFields : ", (response as? HTTPURLResponse)?.allHeaderFields ?? "")
                print("msg : ", (response as? HTTPURLResponse)?.description ?? "")
                print("====================================")
                print("")
                return
            }

            // [response 데이터 획득, json 형태로 변환]
            let resultCode = (response as? HTTPURLResponse)?.statusCode ?? 0
            let resultLen = data! // 데이터 길이
            do {
                guard let jsonConvert = try JSONSerialization.jsonObject(with: data!) as? [String: Any] else {
                    print("")
                    print("====================================")
                    print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                    print("error : ", "json 형식 데이터 convert 에러")
                    print("====================================")
                    print("")
                    return
                }
                guard let JsonResponse = try? JSONSerialization.data(withJSONObject: jsonConvert, options: .prettyPrinted) else {
                    print("")
                    print("====================================")
                    print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                    print("error : ", "json 형식 데이터 변환 에러")
                    print("====================================")
                    print("")
                    return
                }
                guard let resultString = String(data: JsonResponse, encoding: .utf8) else {
                    print("")
                    print("====================================")
                    print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                    print("error : ", "json 형식 데이터 >> String 변환 에러")
                    print("====================================")
                    print("")
                    return
                }
                print("")
                print("====================================")
                print("[A_Image >> requestPOST() :: 사진 업로드 요청 성공]")
                print("allHeaderFields : ", (response as? HTTPURLResponse)?.allHeaderFields ?? "")
                print("resultCode : ", resultCode)
                print("resultLen : ", resultLen)
                print("resultString : ", resultString)
                print("====================================")
                print("")
            } catch {
                print("")
                print("====================================")
                print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                print("error : ", "Trying to convert JSON data to string")
                print("====================================")
                print("")
                return
            }
        }.resume()
    }

    

} // [클래스 종료]



// MARK: [앨범 선택한 이미지 정보를 확인 하기 위한 딜리게이트 선언]
extension A_Image: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    // MARK: [사진, 비디오 선택을 했을 때 호출되는 메소드]
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let img = info[UIImagePickerController.InfoKey.originalImage]{
            
            // [앨범에서 선택한 사진 정보 확인]
            print("")
            print("====================================")
            print("[A_Image >> imagePickerController() :: 앨범에서 선택한 사진 정보 확인 및 사진 표시 실시]")
            //print("[사진 정보 :: ", info)
            print("====================================")
            print("")
            
            
            // [이미지 뷰에 앨범에서 선택한 사진 표시 실시]
            self.imageView.image = img as? UIImage
            
            
            // [이미지 데이터에 선택한 이미지 지정 실시]
            self.imageData = (img as? UIImage)!.jpegData(compressionQuality: 0.8) as NSData? // jpeg 압축 품질 설정
            /*
            print("")
            print("===============================")
            print("[A_Image >> imagePickerController() :: 앨범에서 선택한 사진 정보 확인 및 사진 표시 실시]")
            print("[imageData :: ", self.imageData)
            print("===============================")
            print("")
            // */
            
            
            // [멀티파트 서버에 사진 업로드 수행]
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // [1초 후에 동작 실시]
                self.requestPOST()
            }
        }
        // [이미지 파커 닫기 수행]
        dismiss(animated: true, completion: nil)
    }
    
    
    
    // MARK: [사진, 비디오 선택을 취소했을 때 호출되는 메소드]
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        print("")
        print("===============================")
        print("[A_Image >> imagePickerControllerDidCancel() :: 사진, 비디오 선택 취소 수행 실시]")
        print("===============================")
        print("")
        
        // [이미지 파커 닫기 수행]
        self.dismiss(animated: true, completion: nil)
    }
}
 

[결과 출력]

 

 
반응형
Comments