- Published on
๐ฑ iOS - ๋ค์ด์ํ๋ง
- Authors
- Name
- ์ด์ฐฝ์ค
๋ค์ด์ํ๋ง์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ์ด๋ฏธ์ง ์ฒ๋ฆฌํ๊ธฐ
์ด์ ๊ฒ์๋ฌผ์์ ์์๋ณด์๋ PHPicker
๋ฅผ ์ฌ์ฉํด์ ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ์ค ์๋์ ๊ฐ์ ์๋ฌ๋ฅผ ๋ง์ฃผ์ณค์ต๋๋ค.
Swift๊ฐ ํ์ฉํ 4,194,304 ๋ฐ์ดํธ๋ณด๋ค ํฐ ๋ฐ์ดํฐ๋ฅผ UserDefaults์ ์ ์ฅํ ์ ์๋ค๋ ์๋ฌ์ ๋๋ค.
UserDefaults๋ ์์ ๋ฐ์ดํฐ๋ค์ ์ ์ฅํ ๋ชฉ์ ์ผ๋ก ๋ง๋ค์ด์ง ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์์ ๊ฐ์ ์๋ฌ๋ ์์ฐ์ค๋ฌ์ด ํ์์ธ ๋ฏ ํฉ๋๋ค.
์ง๊ธ ์ ์ฅํ๋ ค๋ ์ด๋ฏธ์ง๋ ์๋ณธ ์ด๋ฏธ์ง ๊ทธ ์์ฒด์์ผ๋ ๋น์ฐํ ์ฉ๋์ด ์ปธ์ ๊ฒ์ด๊ณ , ์ด๋ฏธ์ง์ ์ฉ๋์ ์ค์ด๋ ค๋ฉด ๋ณดํต ์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ ์ค์ด๊ฒ ๋ค๋ ์๊ฐ๋ถํฐ ํ๊ฒ ์ฃ ?
์ด๋ฏธ์ง ํฌ๊ธฐ์ ์ถ์๋ ๊ฐ์ฅ ์ง๊ด์ ์ผ๋ก ์ด๋ฏธ์ง ์ฉ๋์ ์ค์ด๊ณ ๋ฉ๋ชจ๋ฆฌ ๊ฐ์ฉ๋ฅ ๋ ๋์ด๋ ์ข์ ๋ฐฉ๋ฒ ์ค ํ๋์ ๋๋ค.
๊ทธ๋์ ์ ๋ ์ด๋ฏธ์ง๋ฅผ ์ค์ผ ์ ์๋ ๋ฐฉ๋ฒ๋ถํฐ ์ฐพ์๋ณด๊ฒ ๋์์ฃ .
์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ฆ์์ CPU์ ๋ฉ๋ชจ๋ฆฌ ๋์
ํ์ง๋ง ๊ณต๋ถ๋ฅผ ํ ์ง ์ผ๋ง ์ง๋์ง ์์ ๋จ์ํ ์ด๋ฏธ์ง๋ฅผ ์ค์ด๋ ๋ฐฉ๋ฒ์ ์ข์ ๋ฐฉ๋ฒ์ด ์๋๊ณ , ์คํ๋ ค ์ฑ์ด ๊ธฐ๊ธฐ์ ๊ณผ๋ถํ๋ฅผ ๋ ์ค ์ ์๋ค๋ ์ฌ์ค์ ๊นจ๋ฌ์์ต๋๋ค.
๊ทธ ๊น๋ญ์ 2018๋ ์ ์งํ๋ WWDC2018์์ ์ฐพ์๋ณผ ์ ์์์ต๋๋ค.
Image and Graphics Best Practices - WWDC18 - Videos - Apple Developer
์ด๋ฏธ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ๊ฒ์๋ฌผ๋ค์์ ๋ง์ด ๋ค๋ฃจ๊ณ ์๊ณ iOS์ ํ๋ฉด ํํ ๋ฐฉ๋ฒ์ ํ์ตํด๋ณผ ์ ์๋ ์ธ์ ์ ๋๋ค.
์๋๋ UIGraphicsImageRenderer
๋ฅผ ์ฌ์ฉํ์ฌ PHPicker
๋ฅผ ํตํด ๋ฐ์ ์ด๋ฏธ์ง ์ฌ๋ฌ๊ฐ์ ํฌ๊ธฐ๋ฅผ ์ค์ด๋ ์ฝ๋์
๋๋ค.
var images: [UIImage] = []
var result in results {
result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
guard let image = object as? UIImage else { return }
guard let resizedImage: UIImage = UIGraphicsImageRenderer(size: CGSize(width: 2_000, height: 2_000)).image { context in
image.draw(in: CGRect(origin: CGPoint.zero, size: newSize))
} else { return }
images.append(resizedImage)
}
}
๋จ์ํ ๋ณด๊ธฐ์๋ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ์์ด๋ณด์ด์ฃ .
๋ฌธ์ ๋ ๋ ๋ฒ์ ๊ฑธ์ณ์ ๋ฐ์ํฉ๋๋ค.
1. ์๋ณธ์ ๋ก๋ํ๋ค.
guard let image = object as? UIImage else { return }
์ฒซ ๋ฒ์งธ ๋ฌธ์ ์ ๋๋ค.
์ด๋ฏธ์ง ์ฌ์ด์ฆ๋ฅผ ์ค์ด๋ ๋ชฉ์ ์ด ๋ฌด์์ด์๋์?
๋ฉ๋ชจ๋ฆฌ์ ๋ถํ๋ฅผ ๋ ์ฃผ๊ธฐ ์ํด์์์ฃ .
์ ๋ฐฉ๋ฒ์ ์ฑ๊ณต์ ์ผ๋ก ์ํ๋ง ๋๋ค๋ฉด ๊ทธ ์ดํ์๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ ์ ๋ฎ์ถฐ์ค๊ฒ๋๋ค.
๊ทธ๋ฐ๋ฐ ์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ ์ค์ด๊ธฐ ์ํด์ ์ฐ์ ์๋ณธ ์ด๋ฏธ์ง๋ฅผ ํ ๋ฒ ๋ก๋ํด์ฃผ์ด์ผ ํฉ๋๋ค.
๊ทธ ํ ๋ฒ ๋ก๋ํ๋ ๊ณผ์ ์์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ๋ค์ ์ข ๋ฃ์ํค๊ฑฐ๋ ์ฌํ๋ฉด ์ฑ ์์ฒด๋ฅผ ์ข ๋ฃ์ํค๋ ํ์์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
์ค์ ๋ก ์ ์ฝ๋๋ง ๋ด๋ PHPicker
๋ ์ ํํ ๋ฐ์ดํฐ๋ฅผ UIImage
ํ์
์ผ๋ก์ ์ ๊ณตํ์ง ์์ต๋๋ค.
๊ตณ์ด ํฌ๊ธฐ๊ฐ ํฐ ๊ทธ ๋ฐ์ดํฐ๋ฅผ ์๋ฌด๋ฐ ์ฒ๋ฆฌ ์์ด UIImage
๋ก ๋ฐ๋ก ๋ณํํ์ฌ ์ฌ์ฉํ๊ณ ์์๋ ๊ฒ์ด์ฃ !
2. ์์ ์ด๋ฏธ์ง๋ฅผ ์ง์ ๊ทธ๋ฆฐ๋ค.
image.draw(in: CGRect(origin: .zero, size: newSize))
๋ ๋ฒ์งธ ๋ฌธ์ ์ ๋๋ค.
์ ๋ฐฉ๋ฒ์ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ ๊ทธ ์ด๋ฏธ์ง์ ์์ ๋ฒ์ ์ draw
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ง์ ๊ทธ๋ ค์ค์ผ ํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
๊ทธ๋ํฝ ์์ ์ ํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ CPU์ ๋ฉ๋ชจ๋ฆฌ ๋ ํ๋์จ์ด ๋ชจ๋์๊ฒ ์์ ์ ๋ถ์ฌํ๋ ๊ฒ์ด๊ณ , ์ด ๋ํ ๋ฉ๋ชจ๋ฆฌ ๋ถํ๋ฅผ ์ค์ด๋ ๊ฒ๊ณผ๋ ๊ฑฐ๋ฆฌ๊ฐ ๋จผ ๊ฒ์ ์ ์ ์์ต๋๋ค.
๋ค์ด ์ํ๋ง
๊ทธ๋ฌ๋ฉด ๋จ์ํ ์ด๋ฏธ์ง๋ฅผ ๋ฆฌ์ฌ์ด์ง ํ๋ ๋ฐฉ๋ฒ ์ธ์ ์ด๋ค ๋ฐฉ๋ฒ์ด ์์๊น์?
๋ฐ๋ก ๋ฐ์ดํฐ๋ฅผ UIImage
๋ก ๋ฐ๋ก ๋ณํํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋๋ผ CGImage
ํ์
์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋์ฝ๋ฉํ ํ์ผ(data buffer) ์์ฒด์ ์ฌ์ด์ฆ๋ฅผ ์ค์ด๋ ๋ค์ด์ํ๋ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
๋ค๋ง ์ด ๋ฐฉ๋ฒ์ Core Graphics
์ด๋ผ๋ ํ ์ธต ๋ low-levelํ ๋ด์ฉ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ฝ๋๊ฐ ์ข ๋ ๊ธธ๊ณ ๋ณต์กํฉ๋๋ค.
func downsample(at url: URL, to pointSize: CGSize, scale: CGFloat) -> Data? {
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return nil }
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else { return nil }
let isPNG: Bool = {
guard let utType = cgImage.utType else { return false }
return (utType as String) == UTType.png.identifier
}()
let destinationProperties = [
kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
] as CFDictionary
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
CGImageDestinationFinalize(imageDestination)
return data as Data
}
์ด์ง์ด์งํ์ฅฌ..? ๐ต
๋ณต์กํด ๋ณด์ด์ง๋ง ๋ ํํธ๋ก ๋๋ ์ ์์ต๋๋ค. ํ ๋ฒ ์ดํด๋ด ์๋ค.
1. CGImage ์์ฑ
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return nil }
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
downsampling์ ํต์ฌ์ด ๋๋ ๋ถ๋ถ์ ๋๋ค.
์ฌ์ค์ ํ์์ ์ธ ๋ก์ง๋ค์ ์ด ์์ ๋ค ์์ต๋๋ค.
Option flag๋ฅผ ์ ์ํด์ฃผ๊ณ ํด๋น ์ต์
์ ํตํด CGImage
๋ฅผ ์์ฑํด์ฃผ๋ ๋ฐฉ์์
๋๋ค.
kCGImageSourceShouldCache: false
์ด๋ฏธ์ง ์์ค์ ๋ํ ์ฐธ์กฐ๊ฐ๋ง์ ์์ฑํ๊ณ CGImageSource๊ฐ ์์ฑ๋๋ ์ฆ์ ๋์ฝ๋ฉ์ ์งํํ์ง ์์ต๋๋ค.kCGImageSourceShouldCacheImmediately: true
์์ ์ต์ ๊ณผ๋ ๋ฐ๋๋ก ๋ค์ด์ํ๋ง ๋ก์ง์ด ์์๋จ๊ณผ ๋์์ ๋์ฝ๋ฉ ์์ ์ ์ํํฉ๋๋ค.kCGImageSourceCreateThumbnailWithTransform: true
๋ค์ด์ํ๋ง๋ ์ด๋ฏธ์ง๋ฅผ ์๋ณธ ์ด๋ฏธ์ง์ ๊ฐ์ ํํ๋ฅผ ๊ฐ๋๋ก ํฉ๋๋ค.
2. PNG ํ๋ฆฌํฐ ๋ณด์กด
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else { return nil }
let isPNG: Bool = {
guard let utType = cgImage.utType else { return false }
return (utType as String) == UTType.png.identifier
}()
let destinationProperties = [
kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
] as CFDictionary
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
CGImageDestinationFinalize(imageDestination)
return data as Data
์ด ๋ถ๋ถ์ ์ฌ์ค ์ ํ์ ์ผ๋ก ๊ตฌํํ๋ฉด ๋๋ ๋ถ๋ถ์ ๋๋ค.
PNG ํ์ฅ์๋ ๋ณดํต ์คํฌ๋ฆฐ์ท๊ณผ ๊ฐ์ด ์ด๋ฏธ์ง์ ํ์ง์ด ์ค์ํ ์ด๋ฏธ์ง๋ค์ ์ฌ์ฉ๋ฉ๋๋ค.
์ด๋ฌํ ์ด๋ฏธ์ง์ ํ์ง์ด ์์๋๋ค๋ฉด ์ด๋ฏธ์ง์ ํฌํจ๋ ํ ์คํธ ๋ฑ์ ์์ธ์ฑ, ๊ฐ๋ ์ฑ์ด ์ค์ํ ์์๋ค์ ์ํฅ์ด ๊ฐ ์ ์์ต๋๋ค.
PNG ํ์ฅ์๋ผ๋ฉด 1.0์ ์์ ์๋ ์์ถ์, ๊ทธ ์ธ์ ํ์ฅ์๋ผ๋ฉด 0.75์ ์์ถ๋ฅ ๋ก ๋ฐ์ดํฐ๋ฅผ ๋์ฝ๋ฉํ์์ต๋๋ค.
PHPicker์์ ์ฌ์ฉ
์ด์ PHPicker
๋ฅผ ์ฌ์ฉํ ๋๋ ์๋์ ๊ฐ์ ์ฝ๋๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
itemProvider.loadObject(ofClass: UIImage.self) { image, error in
// do something
}
์ด๋ฒ ๊ฒ์๋ฌผ์์ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ URL
ํ์
์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋ฐ๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ์ฌ์ฉํด์ผ ํฉ๋๋ค.
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
// do something
}