🌈 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?.. 근데 이미지 추가?라서 멘붕을 겪었지만
한 번 해보고 나니 어떤 식으로 해야 하는지 이해가 되었습니다!
'⭐️ 개발 > 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 |