๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐ŸŽ iOS/iOS Programming

[iOS, Swift] ์ด๋ฏธ์ง€(ํŒŒ์ผ) ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•˜๊ธฐ(multipart/form-data), ์‚ฌ์ง„์•จ๋ฒ”(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)์—์„œ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ(UIImagePickerController)

728x90

โœ”๏ธ GOAL 

๐Ÿฉถ Step 1. ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์‚ฌ์ง„์•จ๋ฒ”(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)์— ์ ‘๊ทผ

  1-1. ์„ ํƒํ•œ ์ด๋ฏธ์ง€๋ฅผ imageView๋กœ ๋ณด์—ฌ์ค€๋‹ค.

  1-2. ์„ ํƒํ•œ ์ด๋ฏธ์ง€ ํ˜น์€ ์นด๋ฉ”๋ผ๋กœ ์ฐ์€ ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•œ๋‹ค.

 

๐Ÿฉถ Step 2. ์„œ๋ฒ„ ํ†ต์‹  ํ›„ response url๋กœ imageView์— ๋ณด์—ฌ์ค€๋‹ค.


โœ”๏ธ SETTING

1. Info.plist ์ˆ˜์ •

privacy - Photo Library Usage Description, Privacy - Camera Usage Description ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์ง„์•จ๋ฒ”, ์นด๋ฉ”๋ผ ์ ‘๊ทผ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ํ—ˆ๊ฐ€ + Description์— ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋ฅผ ๊ธฐ์žฌํ•œ๋‹ค.

์ด๋•Œ, ์ด์œ ๋Š” ์นด๋ฉ”๋ผ, ์•จ๋ฒ” ํ—ˆ๊ฐ€ ์š”์ฒญ ์•Œ๋ฆผ ์ฐฝ์— ๋ฉ”์‹œ์ง€๋กœ ๋‚˜์˜ค๋Š”๋ฐ,

๋ถˆ์นœ์ ˆํ•œ ๊ฒฝ์šฐ, ์ถ”ํ›„ ์•ฑ์Šคํ† ์–ด์— ์•ฑ ๋ฐฐํฌํ•  ๋•Œ, ๋ฆฌ์ ์‚ฌ์œ ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค. ์นœ์ ˆ + ์ƒ์„ธํ•˜๊ฒŒ ์ ์„ ๊ฒƒ!


๐Ÿฉถ Step 1. ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์‚ฌ์ง„์•จ๋ฒ”(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)์— ์ ‘๊ทผ

1. UIImagePickerController ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

UIImagePickerController๋Š” ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•ด์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ, source type์— ๋”ฐ๋ผ ์นด๋ฉ”๋ผ๋กœ ์ดฌ์˜ํ•œ ์‚ฌ์ง„, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์ง„, ๋™์˜์ƒ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

 

์ด UIImagePickerController๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•„๋ž˜ ๋‘ ๊ฐœ์˜ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋ฅผ ์ƒ์†๋ฐ›๋Š”๋‹ค.

  • UIImagePickerControllerDelegate : ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜๊ณ  ์นด๋ฉ”๋ผ๋ฅผ ์ฐ์—ˆ์„ ๋•Œ ๋‹ค์–‘ํ•œ ๋™์ž‘ ์ˆ˜ํ–‰
  • UINavigationControllerDelegate : ์•จ๋ฒ” ์‚ฌ์ง„์„ ์„ ํƒํ–ˆ์„ ๋•Œ, ํ™”๋ฉด ์ „ํ™˜์„ ๋„ค๋น„๊ฒŒ์ด์…˜์œผ๋กœ ์ด๋™

UIImagePickerController ์ธ์Šคํ„ด์Šค์˜ delegate = self๋กœ ์„ค์ •ํ•œ๋‹ค.

*UIImagePickerControllerDelegate์˜ delegate ์†์„ฑ์€ ์œ„ ๋‘ ๊ฐœ์˜ ํ”„๋กœํ† ์ฝœ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” Protocol Composition์œผ๋กœ ๋‘ ํ”„๋กœํ† ์ฝœ์€ ํ•จ๊ป˜ ์จ์ค˜์•ผ ํ•œ๋‹ค!

class AddViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet weak var itemImg: UIImageView!
    let imgPickerController = UIImagePickerController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        imgPickerController.delegate = self
    }
 }

 

2. ์•จ๋ฒ” ์ ‘๊ทผ ์•ก์…˜ ์ˆ˜ํ–‰

๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์•จ๋ฒ”์ด ์ˆ˜ํ–‰๋˜๋„๋ก imagePickerController์˜ sourceType์„ photoLibrary๋กœ ์„ค์ •

imagePickerController๋Š” UIViewController๋ฅผ ์ƒ์†๋ฐ›๋Š” ์ปจํŠธ๋กค๋Ÿฌ์ด๊ธฐ ๋•Œ๋ฌธ์—,

present ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ปจํŠธ๋กค๋Ÿฌ ๋ทฐ๋ฅผ ๋„์›Œ์ค€๋‹ค!

@IBAction func addImgAction(_ sender: Any) {
	// ์ด๋ฏธ์ง€ ์†Œ์Šค๋กœ ์‚ฌ์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ
	imgPickerController.sourceType = .photoLibrary
    // ์ด๋ฏธ์ง€ ํ”ผ์ปค ์ปจํŠธ๋กค๋Ÿฌ ์‹คํ–‰
	self.present(imgPickerController, animated: true, completion: nil)
}

 

1-1. ์„ ํƒํ•œ ์ด๋ฏธ์ง€๋ฅผ imageView๋กœ ๋ณด์—ฌ์ค€๋‹ค.

UIImageView ๊ฐ์ฒด์˜ ์•„์šธ๋ › ๋ณ€์ˆ˜์— ์„ ํƒํ•œ ์ด๋ฏธ์ง€๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ๋ฒ•! ์œ„ ๋ฐฉ๋ฒ•์œผ๋กœ ์•จ๋ฒ”์„ ์ˆ˜ํ–‰ํ–ˆ์œผ๋ฉด, ์•จ๋ฒ” ๋‚ด์— ์‚ฌ์šฉํ•  ์‚ฌ์ง„์„ ์„ ํƒํ•  ๊ฒƒ์ด๋‹ค. ๋จผ์ €, ์ด ์„ ํƒ์„ ๊ฐ์ง€ํ•˜๋Š” ๋ฉ”์†Œ๋“œ์™€ ์„ ํƒ์„ ์ทจ์†Œํ–ˆ์„ ๋•Œ ์ˆ˜ํ–‰ํ•˜๋Š” ์•„๋ž˜ ๋‘ ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๋‹ค.

  • imagePickerController(_: didFinishPickingMediaWithInfo:) : ์ด๋ฏธ์ง€ ํ”ผ์ปค ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์นด๋ฉ”๋ผ ์ดฌ์˜์„ ์™„๋ฃŒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์†Œ๋“œ
  • imagePickerControllerDidCancel : ์ด๋ฏธ์ง€ ํ”ผ์ปค ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋œ ํ›„ ์ด๋ฏธ์ง€ ์„ ํƒ ์—†์ด ๊ทธ๋ƒฅ ์ทจ์†Œํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์†Œ๋“œ

๋‚˜๋Š” ํฌํ†  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(๊ฐค๋Ÿฌ๋ฆฌ)์—์„œ ์ด๋ฏธ์ง€ ์„ ํƒ ํ›„ ์ˆ˜ํ–‰๋˜๋Š”  didFinishPickingMediaWithInfo ๋ฉ”์†Œ๋“œ์—์„œ ๊ฐ€์ง€๊ณ  ์˜จ ์ด๋ฏธ์ง€๋ฅผ UIImage๋กœ ๋ณ€ํ™˜ ํ›„ photoImageView์˜ ์ด๋ฏธ์ง€๋กœ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

์ด๋•Œ, infoKey ์„ค์ •์— ๋”ฐ๋ผ ์›ํ•˜๋Š” ๊ฐ’์„ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

  • UIImagePickerController.infoKey.mediaType : ์ „๋‹ฌ๋ฐ›์€ ๋ฏธ๋””์–ด ํƒ€์ž…์— ๋Œ€ํ•œ ์ •๋ณด
  • UIImagePickerController.infoKey.originalImage : ์„ ํƒํ•œ ์ด๋ฏธ์ง€์— ๋Œ€ํ•œ ์›๋ณธ ๋ฐ์ดํ„ฐ
  • UIImagePickerController.infoKey.editedImage : ์ˆ˜์ •๋œ ์ด๋ฏธ์ง€๋ฅผ ์ „๋‹ฌ
  • UIImagePickerController.infoKey.cropRect : ํฌ๋กญ ํ•œ ์ด๋ฏธ์ง€์ผ ๊ฒฝ์šฐ, ํฌ๋กญ ๋œ ์ด๋ฏธ์ง€๋ฅผ ์ „๋‹ฌ

UIImageView ๋ณ€์ˆ˜์— ์ด๋ฏธ์ง€๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๊ณ , UI ์ฒ˜๋ฆฌ๋ผ GCD ์ ์šฉํ•ด ์ฃผ์—ˆ๋‹ค.

์ดํ›„, dismiss๋ฅผ ํ†ตํ•ด picker๋ฅผ ๋‹ซ๊ณ  ์ด์ „ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค.

์ด๋ฏธ์ง€ ์„ ํƒ๊ณผ ๋™์‹œ์— didFinishPickingMediaWithInfo ๋ฉ”์†Œ๋“œ๊ฐ€ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ๊ณผ ์„ ํƒํ•œ ์ด๋ฏธ์ง€๊ฐ€ ์ž˜ ์ถœ๋ ฅ๋˜์—ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์„ ํƒํ•œ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์‰ฝ๋‹ค! ์ด์ œ ์ด ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„์— ์˜ฌ๋ ค๋ณด์ž.


1-2. ์„ ํƒํ•œ ์ด๋ฏธ์ง€ ํ˜น์€ ์นด๋ฉ”๋ผ๋กœ ์ฐ์€ ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•œ๋‹ค.(with. alamofire)

asset์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€ ๋˜๋Š” ์œ„์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์˜ฌ๋ฆฐ ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•ด ๋ณด์ž.

โญ๏ธ Alamofire-upload ์‚ฌ์šฉํ•˜์—ฌ ์„ ํƒํ•œ ์‚ฌ์ง„์„ ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ data๋กœ ์ „ํ™˜ํ•ด์„œ ๋ฉ€ํ‹ฐํŒŒํŠธ๋กœ ์ „์†กํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ

Content-Type ์ด multipart/form-data์ธ ํƒ€์ž…์˜ ํ†ต์‹ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ฌ์–ด์„œ post๋ฅผ ๋‚ ๋ฆฌ๋Š” ๊ฑฐ๋ผ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค!

 

1. URL, Request Header ์ž‘์„ฑํ•˜๊ธฐ

HTTP message body์— ๋“ค์–ด๊ฐ€๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ HTTP ํ—ค๋”์— ๋ช…์‹œํ•ด ์ค€๋‹ค.

์ด๋•Œ, ์‚ฌ์šฉํ•˜๋Š” ํ•„๋“œ๊ฐ€ Content-Type์ด๊ณ  multipart ํƒ€์ž…์œผ๋กœ ๊ฐ’์œผ๋กœ ํ—ค๋”์— ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

*MultiPart ์ด๋ฏธ์ง€๋ฅผ data๋กœ ์ „ํ™˜ํ•ด์„œ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹

// ์„œ๋ฒ„ url
let url: String = "~~~"
        
let header: HTTPHeaders = [
	"Content-Type" : "multipart/form-data"
]

 

2. requester body ๊ตฌ์„ฑํ•˜๊ธฐ

์„œ๋ฒ„ API์— ๋งž๊ฒŒ ๋งž๋Š” body๋กœ ๊ตฌ์„ฑํ•ด์ค€๋‹ค. ์—†์œผ๋ฉด ์•ˆ ๋„ฃ์–ด์ค˜๋„ ๋จ.

let parameters: [String : Any] = [:]

*ํŒŒ์ผ ์—…๋กœ๋“œ ํ•  ๋•Œ, HTTP ๊ทœ์•ฝ์„ ๋ณด๋ฉด,

ํŒŒ์ผ ์—…๋กœ๋“œ API

boundary์˜ ๋ฌธ์ž์—ด ์ค‘ ๋งˆ์ง€๋ง‰ **------WebKitFormBoundaryQGvWeNAiOE4g2VM5--** ๊ฐ’์€ ๋‹ค๋ฅธ ๊ฐ’๊ณผ ๋‹ค๋ฅด๊ฒŒ --๊ฐ€ ๋งˆ์ง€๋ง‰์— ๋ถ™์—ˆ๋Š”๋ฐ, -- ๋Š” body์˜ ๋์„ ์•Œ๋ฆฌ๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„๋‹ค.

URLSession์œผ๋กœ ํŒŒ์ผ์—…๋กœ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ์•„๋ž˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

let boundary = "Boundary-\(UUID().uuidString)"

ํ•˜์ง€๋งŒ, alamofire์„ ์‚ฌ์šฉํ•˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด alamofire์˜ MultipartFormData ๊ตฌํ˜„ํ•ด์ฃผ๊ณ  ์žˆ์–ด ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

alamofire์˜ MultipartFormData ๋‚ด๋ถ€ ์ฝ”๋“œ

 

3. multipartformdata์— ์ถ”๊ฐ€ํ•  ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

body ๊ฐ’๊ณผ ์—…๋กœ๋“œํ•  ์ด๋ฏธ์ง€๋ฅผ multipartformdata์— ์ถ”๊ฐ€ ์ค€๋‹ค.

body์— ๋„ฃ์–ด์ค˜์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋“ค์˜ ํƒ€์ž…์€ Data ํƒ€์ž…์ด์–ด์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ๊ฐ€ String, int, double ๋“ฑ์˜ ํƒ€์ž…์ด๋ฉด ๋ชจ๋‘ Data ํ˜•์‹์œผ๋กœ ๋ฐ”๊ฟ”์„œ ๋ณด๋‚ด์•ผ ํ•œ๋‹ค.

 

  • ๊ธฐ๋ณธ ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
    • String: utf8๋กœ ๋จผ์ € ์ธ์ฝ”๋”ฉ ํ›„ Data()๋กœ ๋ณ€ํ™˜ ex) Data(stringdata.utf8)
let content = "๋‚ด์šฉ"

multipartFormData.append(Data(content.utf8 ?? "".utf8), withName: "content")
  • ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
    • withName - key๊ฐ’
    • fileName - ์„œ๋ฒ„์— ์—…๋กœ๋“œํ•  ํŒŒ์ผ ์ด๋ฆ„
    • mimeType - ํŒŒ์ผ ํ˜•์‹
    • ์ฃผ์˜* fileName์— jpeg ๋˜๋Š” png ํ™•์žฅ์ž๋ฅผ ๊ผญ ๋ถ™์—ฌ์ค˜์•ผ ํ•œ๋‹ค. 
      • ex) fileName: image.jpeg , memeType: image/jpeg
AF.upload(multipartFormData: { MultipartFormData in
	// body ์ถ”๊ฐ€
	for (key, value) in parameters {
		MultipartFormData.append("\(value)".data(using: .utf8)!, withName: key)
	}
	// UIImage ์ฒ˜๋ฆฌ
        // img ์ถ”๊ฐ€ data๋กœ ํƒ€์ž… ๋ณ€๊ฒฝ ํ•„์š”!
	if let image = imageData?.pngData() {
		MultipartFormData.append(image, withName: "file", fileName: "test.png", mimeType: "image/png")
	}
    ...

*์ฃผ์˜*

๊ทธ๋Ÿฐ๋ฐ, ์•จ๋ฒ”์—์„œ ํ˜น์€ ์ดฌ์˜ํ•œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๊ฐ€ ์šฉ๋Ÿ‰์ด ๋„ˆ๋ฌด ์ปค์„œ ์—…๋กœ๋“œ๊ฐ€ ๋˜์ง€ ์•Š์•˜๋‹ค.

์ค‘๊ฐ„์— ์ด๋ฏธ์ง€ ์šฉ๋Ÿ‰์„ ์ค„์ด๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ๋‹ˆ ์„ฑ๊ณต!

๊ธฐ์กด ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ๋ณด๋‚ธ ์›๋ณธ ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ๋Š” ๊ฐ€๋กœ์„ธ๋กœ 3000px ๋‚ด์™ธ๋กœ jpeg compression์„ 1๋กœ ํ•˜๋ฉด 6MB ์ •๋„, 0.5๋กœ ํ•˜๋ฉด 3MB ์ •๋„ ๋˜์—ˆ๋‹ค. compression์„ ๋” ์ค„์ด์ž๋‹ˆ ํ™”์งˆ์ด ์•ˆ ์ข‹์•„์ง€๊ณ  ์‚ฌ์šฉํ•  ์‚ฌ์ง„ ํฌ๊ธฐ๊ฐ€ ์ž‘์•„์„œ, resize๋กœ ์šฉ๋Ÿ‰์„ ์ค„์˜€๋‹ค.

 

[๋ฆฌ์‚ฌ์ด์ฆˆ ์ฝ”๋“œ]

let resizedImage = resizeImage(image: image, newWidth: 300)
extension UIImage{
    func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage {
        let scale = newWidth / image.size.width // ์ƒˆ ์ด๋ฏธ์ง€ ํ™•๋Œ€/์ถ•์†Œ ๋น„์œจ
        let newHeight = image.size.height * scale
        UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
        image.draw(in: CGRectMake(0, 0, newWidth, newHeight))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
    }
}

 

[์›๋ณธ์‚ฌ์ง„ ์šฉ๋Ÿ‰]

imageData Foundation.Data? 9604841 bytes some

[๋ฆฌ์‚ฌ์ด์ฆˆ ํ›„ ์šฉ๋Ÿ‰]

imageData Foundation.Data? 102521 bytes some

 

4. response ์ฒ˜๋ฆฌ

์ •์ƒ์ ์œผ๋กœ ์—…๋กœ๋“œ๋˜์–ด response ๊ฐ’์„ ๋ฐ›์•˜๊ณ , json ๋””์ฝ”๋”ฉ ์ฒ˜๋ฆฌ ํ•ด์ฃผ์—ˆ๋‹ค.

 


๐Ÿฉถ Step 2. ์„œ๋ฒ„ ํ†ต์‹  ํ›„ response url๋กœ imageView์— ๋ณด์—ฌ์ค€๋‹ค.

ํ†ต์‹ ์— ์„ฑ๊ณตํ•˜์—ฌ ๋ฐ›์€ remote url๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ์ž~

๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ Data(contentsOf: )๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ URLSession์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ• 2๊ฐ€์ง€ ์žˆ๋Š”๋ฐ, ์•„์ฃผ ๊ฐ„๋‹จํ•˜๋‹ค.(KingFisher์—๋„ ์ง€์›!)

 

*ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” remote url

: https://fastly.picsum.photos/id/8/5000/3333.jpg?hmac=OeG5ufhPYQBd6Rx1TAldAuF92lhCzAhKQKttGfawWuA

 

Sol 1. Data(contentsOf: ) ์‚ฌ์šฉํ•˜๊ธฐ

์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋‹ค. 

    let url = URL(string: "https://cdn.cocoacasts.com/cc00ceb0c6bff0d536f25454d50223875d5c79f1/above-the-clouds.jpg")!

    if let data = try? Data(contentsOf: url) {
        imageView.image = UIImage(data: data)
    }

ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ์‹คํ–‰์€ ๋˜์ง€๋งŒ Synchronous URL loading ์Šค๋ ˆ๋“œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๋™๊ธฐ url ๋กœ๋”ฉํ•˜๋ฉด UI ์‘๋‹ต์ด ์—†์„ ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค! 

๊ทธ๋Ÿผ ๋น„๋™๊ธฐ๋กœ ๋ฐ”๊ฟ”์ฃผ์ž!

์–ด๋–ป๊ฒŒ? ์œ„ ๋ฉ”์‹œ์ง€์—์„œ ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋น„๋™๊ธฐ ๋„คํŠธ์›Œํฌ API์ธ URLSession ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜

ํ˜น์€ ์•„๋ž˜์™€ ๊ฐ™์ด GCD๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…ํ•˜์ž!

    let url = URL(string: "https://cdn.cocoacasts.com/cc00ceb0c6bff0d536f25454d50223875d5c79f1/above-the-clouds.jpg")!
	// ๋น„๋™๊ธฐ ์ ์šฉ 
    DispatchQueue.global().async {
        if let data = try? Data(contentsOf: url) {
        	// UI ์ž‘์—…์€ main ์—์„œ!
            DispatchQueue.main.async {
                self.imageView.image = UIImage(data: data)
            }
        }
    }

Sol 2. URLSession.shared.dataTask(with: url) ์‚ฌ์šฉํ•˜๊ธฐ

URLSession.shared.dataTask()์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๋ถ€ UI ๊ด€๋ จ ์ฒ˜๋ฆฌ๋Š” ๋ฉ”์ธ์Šค๋ ˆ๋“œ๋กœ ์ „ํ™˜ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

        if let url = URL(string: https://cdn.cocoacasts.com/cc00ceb0c6bff0d536f25454d50223875d5c79f1/above-the-clouds.jpg")! {
            let task = URLSession.shared.dataTask(with: url) { data, response, error in
                guard let data = data, error == nil else { return }
				// UI๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ
                DispatchQueue.main.async { 
                    self.imgView.image = UIImage(data: data)
                }
            }
            task.resume()
        }

+ UIImageView์—์„œ ์ž์ฃผ ๋ถ€๋ฅด๋ฉด, extension์œผ๋กœ ๋งŒ๋“ค์–ด์ค˜์„œ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„ ๊น”๋”ํ•˜๊ฒŒ refac ํ•ด์ฃผ๊ธฐ!

self.myPageImg.loadwithURLSession(url: URL(string:thumbnail)!)
extension UIImageView {
    func loadwithURLSession(url: URL) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else { return }
            DispatchQueue.main.async { 
                self.image = UIImage(data: data)
            }
        }
        task.resume()
    }
}

[์ „์ฒด ์ฝ”๋“œ]

์•จ๋ฒ”์—์„œ ์‚ฌ์ง„ ์„ ํƒ!

    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            
            let ala = AlamofireManager()
            let resizedImage = resizeImage(image: image, newWidth: 300)

            ala.fileUpload(img: resizedImage, completion: { res in
                let url = URL(string: res)
                self.imgView.loadwithURLSession(url: url!)
            })
            self.dismiss(animated: true, completion: nil)
        }
    }

ํŒŒ์ผ ์—…๋กœ๋“œ ํ›„ response url ๊ฐ’ loadwithURLSession๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด์„œ ์ด๋ฏธ์ง€ ์ถœ๋ ฅ!

    func fileUpload(img: UIImage, completion: @escaping (String)->Void) {
        // ์„œ๋ฒ„ url
        let url: String = "http://3.39.17.117:80/archive-files"
       
        // ํ—ค๋” ๊ตฌ์„ฑ : Content-Type ํ•„๋“œ์— multipart ํƒ€์ž…์ถ”๊ฐ€
        let header: HTTPHeaders = [
            "Accept" : "application/json, application/javascript, text/javascript, text/json",
            "Content-Type" : "multipart/form-data"
        ]
        
        let parameters: [String : Any] = [:]
        let imageData = img.jpegData(compressionQuality: 1)
             
        AF.upload(multipartFormData: { MultipartFormData in
            // body ์ถ”๊ฐ€
            for (key, value) in parameters {
                MultipartFormData.append("\(value)".data(using: .utf8)!, withName: key)
            }
            // img ์ถ”๊ฐ€ data๋กœ ํƒ€์ž… ๋ณ€๊ฒฝ ํ•„์š”!
            if let image = imageData {
                MultipartFormData.append(image, withName: "file", fileName: "test.jpeg", mimeType: "image/jpeg")
            }
        }, to: url, method: .post, headers: header)
        .validate()
        .responseDecodable(of: GetUrl.self, completionHandler:  { response in
            switch response.result{
            case .success(let data):
                print("url : \(data.url)")
                completion(data.url)
            case .failure(let error):
              print(error)
            }
        })
    }

 

๋ฐ˜์‘ํ˜•