훌이
후리스콜링개발
훌이

블로그 메뉴

  • 왈 (iOS APP)
  • Github
전체 방문자
오늘
어제
  • 전체 (171)
    • ⭐️ 개발 (140)
      • JAVA (4)
      • Web (5)
      • iOS & Swift (94)
      • iOS Concurrency (4)
      • Rx (18)
      • Git (6)
      • WWDC (1)
      • Code Refactor (3)
      • Server (1)
    • ⭐️ Computer Science (22)
      • 운영체제 (10)
      • 네트워크 (5)
      • PS (7)
    • 경제시사상식 (8)
    • 기타 등등 (0)

인기 글

최근 글

06-30 08:36

티스토리

hELLO · Designed By 정상우.
훌이

후리스콜링개발

[iOS] Alamofire Multipart/formdata를 통한 이미지 서버 통신
⭐️ 개발/iOS & Swift

[iOS] Alamofire Multipart/formdata를 통한 이미지 서버 통신

2021. 7. 19. 14:18
728x90
반응형

🌈 Multipart/formdata 형식를 통한 이미지 POST 방식

→ 멀티파트는 Alamofire에서 제공해주는 기능으로 이미지를 data로 전환해서 전송하는 방식입니다.

multipart/form-data 는 파일 업로드가 있는 양식요소에 사용되는 enctype 속성의 값중 하나이고, multipart는 폼데이터가 여러 부분으로 나뉘어 서버로 전송되는 것을 의미합니다.

 

 

 

우선, 서버에서 넘겨준 API 문서입니다.

 

👀 주의해서 봐야 할 것은

Content-Type 이 multipart/form-data 라는 점입니다.

따라서, request header 배열 영역에 잘 입력해줘야 합니다.

 

👀 두 번째로 Body 부분에서 이미지를 제외하고도

우리가 업로드 해줄 데이터가 있을 경우에는

multipart를 통해 분리해서 업로드 해줘야 합니다.

 

이때 필요한 것이 바로 value - key 그리고 각 타입이기 때문에 항상 API 문서를 주의깊게 볼 필요가 있습니다!

 


AddActionEditService

  • 먼저 전체 코드는 이렇습니다.
import Foundation
import Alamofire

struct AddActionEditService {
    
    static let shared = AddActionEditService()
    
    func editActivity (imageData: UIImage?,
                            content: String,
                            year: String,
                            month: String,
                            day: String,
                            index: Int,
                            activityIndex: Int,
                            completion: @escaping (NetworkResult<Any>) -> Void) {
        
        let URL = APIConstants.editURL
        let header : HTTPHeaders = [
            "Content-Type" : "multipart/form-data",
            "token" : GeneralAPI.token ]
        
        let parameters: [String : Any] = [
            "activityContent": content,
            "activityYear": year,
            "activityMonth": month,
            "activityDay": day,
            "characterIndex": index,
            "activityIndex": activityIndex
        ]
        AF.upload(multipartFormData: { multipartFormData in
            for (key, value) in parameters {
                multipartFormData.append("\(value)".data(using: .utf8)!, withName: key)
            }
            if let image = imageData?.pngData() {
                multipartFormData.append(image, withName: "activityImage", fileName: "\(image).png", mimeType: "image/png")
            }
        }, to: URL, usingThreshold: UInt64.init(), method: .post, headers: header).response { response in
            guard let statusCode = response.response?.statusCode,
                  statusCode == 200
            else { return }
            completion(.success(statusCode))
        }
    }
}

 

기존 Alamofire과 통신 방식은 굉장히 비슷합니다.

URL과 Request Header를 작성해주고

let URL = APIConstants.editURL
        let token = UserDefaultStorage.accessToken
        let header : HTTPHeaders = [
            "Content-Type" : "multipart/form-data",
            "token" : token
        ]

 

 

API 문서의 Request Body를 보고 key - value 를 배열 형식으로 작성해줍니다.

let parameters: [String : Any] = [
            "activityContent": content,
            "activityYear": year,
            "activityMonth": month,
            "activityDay": day,
            "characterIndex": index,
            "activityIndex": activityIndex
        ]

→ 👀 여기서 한 가지 포인트는 어차피 "activityImage"는 다른 String이나 Int 형식의 요청 바디와 다르게

multipart를 통해 따로 data로 변환시켜 데이터를 전송시켜줄 것이기 때문에 따로 parameters 배열에 넣어주지 않아도 됩니다.

 

그 후에 Alamofire에서 제공하는 upload를 통해서 데이터를 전송해 줍니다.

 

 

1️⃣ 첫 번째로 기본 request body를 추가하는 부분입니다.

for (key, value) in parameters {
    multipartFormData.append("\(value)".data(using: .utf8)!, withName: key)
}

→ 요청 바디에 있는 key, value 값을 for문을 통해 각각 multipartFormData 에 추가해서 전송합니다.

 

2️⃣ 두 번째로 이미지를 추가하는 부분입니다.

if let image = imageData?.pngData() {
    multipartFormData.append(image, withName: "activityImage", fileName: "\(image).png", mimeType: "image/png")
}

→ 이미지는 nil값인 경우를 고려하여 if let을 통해서 전송하였고, 그 뒷부분은 어떤 데이터를 어떤 형식으로 서버에 전송할 건지를 의미합니다.

 

👀 withName - key값

👀 fileName - 서버에 업로드할 파일 이름

👀 mimeType - 파일 형식

 

데이터를 보내는 코드를 다 작성 후에 데이터를 전송할 url과 통신 방법과 헤더를 작성 후에 응답을 받아오면 됩니다.

, to: URL, usingThreshold: UInt64.init(), method: .post, headers: header).response { response in
            guard let statusCode = response.response?.statusCode,
                  statusCode == 200
            else { return }
            completion(.success(statusCode))
        }

 

 

VC에 추가해주기!

그 후에 이미지를 전송시켜주는 시점에서 AddActionEditService를 불러와 서버 통신을 완료시켜주면 됩니다.

저는 uploadButton을 누르는 곳에서 이미지를 업로드 해야 하기 때문에 해당 함수 내부에 적어줬습니다.

@objc func touchupUploadButton(_ sender: UIButton) {
        guard let date = self.dateLabel.text?.split(separator: ".") else { print("날짜형식반영완료"); return }
        if isEdited {
            print("수정")
            AddActionEditService.shared.editActivity(imageData: buttonImage,
                                                     content: activityTextView.text!,
                                                     year: String(date[0]),
                                                     month: String(date[1]),
                                                     day: String(date[2]),
                                                     index: characterIndex,
                                                     activityIndex: 36) { result in
                switch result {
                case .success(let msg):
                    print("success", msg)
                    self.dismiss(animated: true, completion: nil)
                case .requestErr(let msg):
                    print("requestERR", msg)
                case .pathErr:
                    print("pathERR")
                case .serverErr:
                    print("serverERR")
                case .networkFail:
                    print("networkFail")
                }
            }
        } else if isEdited == false {
            print("글 생성")
            if activityTextView.text == placholder || activityTextView.text == "" {
                uploadButton.isEnabled = true
            } else {
                uploadButton.isEnabled = false

                AddActionNewService.shared.uploadNewActivity(imageData: buttonImage,
                                                             content: activityTextView.text!,
                                                             year: String(date[0]),
                                                             month: String(date[1]),
                                                             day: String(date[2]),
                                                             index: characterIndex) { result in
                    switch result {
                    case .success(let msg):
                        print("success", msg)
                        self.reloadData?()
                        self.dismiss(animated: true, completion: nil)
                    case .requestErr(let msg):
                        print("requestERR", msg)
                    case .pathErr:
                        print("pathERR")
                    case .serverErr:
                        print("serverERR")
                    case .networkFail:
                        print("networkFail")
                    }
                }
            }
        }
    }

 

isEdited가 있는 이유는 게시글 작성과 수정인 경우의 분기처리를 위해서입니당~

끝~!

 

 

처음에는 후.. 그냥 GET까지는 괜찮은데

로그인, 회원가입만 해봤던 POST?.. 근데 이미지 추가?라서 멘붕을 겪었지만

한 번 해보고 나니 어떤 식으로 해야 하는지 이해가 되었습니다!

 

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'⭐️ 개발 > iOS & Swift' 카테고리의 다른 글

[iOS] Moya가 모야? - Moya로 Get 통신하기  (10) 2021.07.25
[iOS] TextView - 플레이스홀더, 패딩, 글자 수 제한, 커서, 키보드 dismiss  (0) 2021.07.22
[iOS] DateFormatter  (2) 2021.06.24
[iOS] Expandable TableView Cell 만들기  (6) 2021.06.16
[iOS] UIColor Extension 파일 만드는 법 (feat.Zeplin)  (1) 2021.06.07
    '⭐️ 개발/iOS & Swift' 카테고리의 다른 글
    • [iOS] Moya가 모야? - Moya로 Get 통신하기
    • [iOS] TextView - 플레이스홀더, 패딩, 글자 수 제한, 커서, 키보드 dismiss
    • [iOS] DateFormatter
    • [iOS] Expandable TableView Cell 만들기
    훌이
    훌이

    티스토리툴바