iOS

[ iOS ] ScrollView ํ™”๋ฉด ์บก์ณํ•˜๊ธฐ (Image Creation)

Forest Yun 2023. 2. 18. 21:56
728x90

๋ฌธ์ œ

๋ฌด๋‹ค, ๊พธ์ฃผ๋‹ˆ ๋“ฑ ๋ช‡๋ช‡ ์•ฑ๋“ค์€ ํ™”๋ฉด์„ ์บก์ณํ•œ ๋“ฏ ์ด๋ฏธ์ง€ํ™”ํ•ด ์ €์žฅ ๋˜๋Š” ๊ณต์œ ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ์˜ค๋Š˜์€ ์ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์˜ˆ์ •์ธ๋ฐ, ํ™”๋ฉด ์บก์ณ์—์„œ ๋” ๋‚˜์•„๊ฐ€ ํ™”๋ฉด ๋ฐ– ์•„๋ž˜๋กœ ๋‚ด์šฉ์ด ๊ณ„์† ์ด์–ด์ง€๋Š” ScrollView์˜ ์ „์ฒด ํ™”๋ฉด์„ ์บก์ณํ•˜๋Š”, ์ฆ‰ ์ด๋ฏธ์ง€ํ™”ํ•˜๋Š” ์ž‘์—…์„ ์ •๋ฆฌํ•  ๊ฒƒ์ด๋‹ค.
์ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์—ฌ UIActivityViewController๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ์ธ์Šคํƒ€๊ทธ๋žจ์— ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.
 
 

UIGrapics~

UIKit์—๋Š” Image์™€ PDF ์„ ์ƒ์„ฑํ•˜๊ณ  Drawing ํ•  ์ˆ˜ ์žˆ๋Š” ์—ฌ๋Ÿฌ ํด๋ž˜์Šค์™€ ํ•จ์ˆ˜๋“ค์„ ์ œ๊ณตํ•œ๋‹ค. ์ด๋ฒˆ์— ์•Œ์•„๋ณผ ํ•จ์ˆ˜๋Š” Image์™€ PDF์˜ Image Creation ํŒŒํŠธ์— ๋‚˜์˜ค๋Š” ํ•จ์ˆ˜ ์ค‘ ์•„๋ž˜ 3๊ฐœ์™€ Drawing์˜ Shadows/Graphics Context Promitives์˜ ํ•จ์ˆ˜ 1๊ฐœ์ด๋‹ค.

ํ•จ์ˆ˜ ๋ช… ์„ค๋ช…
func UIGraphicsBeginImageContext(_ size: CGSize) ๋น„ํŠธ๋งต ๊ธฐ๋ฐ˜์˜ ๊ทธ๋ž˜ํ”ฝ context๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด context๋ฅผ ํ˜„์žฌ context๋กœ ๋งŒ๋“ ๋‹ค.
func UIGraphicsBeginImageContextWithOptions( _ size: CGSize, _ opaque: Bool, _ scale: CGFloat )
ํŠน์ • ์˜ต์…˜์„ ๊ฐ€์ง„ ๋น„ํŠธ๋งต ๊ธฐ๋ฐ˜์˜ ๊ทธ๋ž˜ํ”ฝ context๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
func UIGraphicsGetImageFromCurrentImageContext() -> UIImage? ํ˜„์žฌ ๋น„ํŠธ๋งต ๊ธฐ๋ฐ˜์˜ ๊ทธ๋ž˜ํ”ฝ context์˜ content๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
func UIGraphicsEndImageContext()
์Šคํƒ์˜ top์—์„œ ๋น„ํŠธ๋งต ๊ธฐ๋ฐ˜์˜ ํ˜„์žฌ ๊ทธ๋ž˜ํ”ฝ context๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.
**context๋Š” stack๊ตฌ์กฐ์ด๋‹ค

 
 
ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „ context์™€ ๋ Œ๋”๋ง์„ ์ดํ•ดํ•ด์•ผํ•œ๋‹ค. context๊ฐ€ ์บ ๋ฒ„์Šค, ๋ Œ๋”๋ง์ด ๊ทธ๋ฆผ ๊ทธ๋ฆฌ๊ธฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์‰ฝ๋‹ค.

  1. UIGraphicBeginImageContext ํ•จ์ˆ˜: ๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ๋•Œ ์บ”๋ฒ„์Šค๊ฐ€ ๋จผ์ € ์ค€๋น„๋˜์–ด์•ผํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์บ”๋ฒ„์Šค(context)๋ฅผ ์ค€๋น„ํ•œ๋‹ค.
  2. render ํ•จ์ˆ˜(์•„๋ž˜์—์„œ ์„ค๋ช…): ๊ทธํ›„ ํ˜„์žฌ ์ค€๋น„๋œ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฐ๋‹ค.
  3. UIGraphicsGetImageFromCurrentImageContext ํ•จ์ˆ˜: ๊ทธ๋ฆผ์„ ๋‹ค ๊ทธ๋ ธ์œผ๋ฉด ์บ”๋ฒ„์Šค์—์„œ ๊ทธ๋ฆผ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
  4. UIGrphicsEndImageContext ํ•จ์ˆ˜: ๋งˆ์ง€๋ง‰์œผ๋กœ ์บ”๋ฒ„์Šค๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ํ™”์‹ค์„ ์ •๋ฆฌํ•œ๋‹ค.

 
๋‚˜๋Š” ์ฒซ ๋ฒˆ์งธ context๋ฅผ ๋งŒ๋“œ๋Š” ๊ณผ์ •์—์„œ ๊ทธ๋ƒฅ BeginImageContext๊ฐ€ ์•„๋‹ˆ๋ผ BeginImageContextWithOptions ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ƒฅ BeginImageContext ํ•จ์ˆ˜๋Š” Options ํ•จ์ˆ˜์˜ scale ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฐ’์ด 1.0์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค. scale์€ ๋น„ํŠธ๋งต์˜ ํฌ๊ธฐ๋ฅผ ๊ตฌํ•˜๊ธฐ ์œ„ํ•ด ๋„ˆ๋น„, ๋†’์ด์™€ ํ•จ๊ป˜ ๊ณฑํ•ด์ ธ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ •ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ™”์งˆ์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์ณ ๋‚ฎ์€ ํ™”์งˆ์˜ ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. scale์ด 0.0์˜ ๊ฒฝ์šฐ ๊ธฐ๊ธฐ์˜ ํ™”๋ฉด scale์„ ์˜๋ฏธํ•˜์—ฌ ์ข‹์€ ํ™”์งˆ์˜ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. 
์œ„์™€ ๊ฐ™์€ ์ด์œ ๋กœ UIGraphicsBeginImageContextWithOptions ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ scale์„ ์กฐ์ •ํ•ด์ค„ ๊ฒƒ์ด๋‹ค.
 
 
 

Rendering

๋ Œ๋”๋ง์€ ์œ„ ์„ค๋ช…์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค. ๋ Œ๋”๋งํ•˜๋Š” ํ•จ์ˆ˜ ๋˜ํ•œ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

๋ฉ”์„œ๋“œ ๋ช…์„ค๋ช…
func drawHierarchy( in rect: CGRect, afterScreenUpdates afterUpdates: Bool ) -> Boolํ˜„์žฌ context์— ํ™”๋ฉด์— ๋ณด์ด๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์™„์ „ํ•œ view ๊ณ„์ธต์˜ snapShot์„ ๋ Œ๋”๋งํ•œ๋‹ค.
์ด ๋ฉ”์„œ๋“œ๋Š” view ์ด๋ฏธ์ง€์— ๋ธ”๋Ÿฌ์™€ ๊ฐ™์€ ๊ทธ๋ž˜ํ”ฝ ํšจ๊ณผ๋ฅผ ๋„ฃ๊ณ ์ž ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. 
func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView?ํ˜„์žฌ view์˜ content์— ๊ธฐ๋ฐ˜ํ•œ snapShot์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
์ด๋ฏธ ๋ Œ๋”๋ง๋œ ํ˜„์žฌ view์˜ ๋ชจ์Šต์„ capture ํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— drawHierarchy ํ•จ์ˆ˜๋ณด๋‹ค ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค.
func render(in ctx: CGContext)์ œ๊ณต๋œ context์— ํ•ด๋‹น layer์™€ ํ•˜์œ„ layer๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.

 
์šฐ๋ฆฌ๋Š” ํ™”๋ฉด ์•ˆ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฐ–์œผ๋กœ ๊ฐ€๋ ค์ง„ ๋ถ€๋ถ„ ๋˜ํ•œ ๋ Œ๋”๋งํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— render ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค. 
 
 
 
 

ScrollView์˜ Content ์˜์—ญ

ScrollView๋Š” frame๊ณผ content ์˜์—ญ์˜ ์˜๋ฏธ๊ฐ€ ๋‹ค๋ฅด๋‹ค. scrollView ์•ˆ์— ๋‚ด์šฉ์ด ๊ธธ์–ด์ง„๋‹ค๋ฉด ๊ทธ๊ฑด content์˜ ์˜์—ญ์ด ๊ธธ์–ด์ง์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰ scrollView.contentSize๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์ด๋‹ค.
 

 
 
์ด ์ ์œผ๋กœ ๋ณด์•„์„œ ์šฐ๋ฆฌ๋Š” scroll์˜ frame์ด contentSize๋งŒํผ ๋Š˜์–ด๋‚˜์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋”ํ•ด์„œ contentOffset์„ .zero(x:0, y:0)๋กœ ๋ณ€๊ฒฝํ•ด content๊ฐ€ ์ฒ˜์Œ ๋‚ด์šฉ๋ถ€ํ„ฐ ์ž˜ ๋ Œ๋”๋ง๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์•ผํ•œ๋‹ค.
 

 

 

๋ Œ๋”๋ง ๋ฌธ์ œ

render ํ•จ์ˆ˜๋Š” ํ•ด๋‹น ๋ทฐ์˜ layer์™€ ํ•˜์œ„ layer๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” scrollView๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ทธ SuperView์ธ view์˜ frame๋„ contentSize๋งŒํผ ์กฐ์ •ํ•  ๊ฒƒ์ด๋ฉฐ scrollView๊ฐ€ ์•„๋‹Œ view์˜ layer๋ฅผ ๋ Œ๋”๋ง ํ•  ๊ฒƒ์ด๋‹ค.
์™œ๋ƒํ•˜๋ฉด render๊ฐ€ ํ•ด๋‹นํ•˜๋Š” view์˜ layer๋ฅผ ๋ Œ๋”๋งํ•ด์ฃผ์ง€๋งŒ ๋งŒ์•ฝ self.view.frame, ์ฆ‰ ๊ธฐ๊ธฐ์˜ ํ™”๋ฉด์— ๊ฐ€๋ ค์ ธ ์•ˆ ๋ณด์ด๋Š” ๋ถ€๋ถ„์€ ๋ Œ๋”๋งํ•ด์ฃผ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์•„๋ž˜ ์‚ฌ์ง„์˜ ๊ฒ€์€์ƒ‰ ๋ถ€๋ถ„์ด ๋ฐ”๋กœ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์€ context ๋ถ€๋ถ„์ด๋‹ค.
 

 
 
๋˜ view.frame์„ contentSize๋กœ ์กฐ์ •ํ•˜์ง€ ์•Š์œผ๋ฉด scrollView์˜ content๊ฐ€ ์งง์•„๋„ ํ™”๋ฉด์˜ ํฌ๊ธฐ๋งŒํผ ๋ Œ๋”๋ง๋˜๊ธฐ ๋•Œ๋ฌธ์— content ์˜์—ญ๋งŒ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” frame์„ ์กฐ์ •ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.
 

 
 
๋˜ ์‹ ๊ฒฝ์จ์•ผ ํ•  ๋ถ€๋ถ„์€ AutoLayout์„ safeAreaLayoutGuide์— ์„ค์ •ํ–ˆ๋Š”์ง€ ์•„๋‹˜ SuperView์ธ view์— ์„ค์ •ํ–ˆ๋Š”์ง€์ด๋‹ค. ์ด์— ๋”ฐ๋ผ NavigationBar ์‚ฌ์ด์ฆˆ๋งŒํผ ์˜ค์ฐจ๊ฐ€ ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ๋‹ค.
 
 
 
 

 

 

๊ตฌํ˜„

 

extension UIView {
    
    func snapShotFullScreen(scrollView: UIScrollView) -> UIImage? {
        //scrollView์˜ contentSize๋งŒํผ scrollView.frame๊ณผ self.view.frame์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ •
        //๊ทธ๋ฆฌ๊ณ  scrollView๊ฐ€ ์•„๋‹Œ self.view์˜ layer๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.
        //๊ทธ ํ›„ scrollView.frame๊ณผ self.view.frame์„ ์›์ƒ๋ณต๊ตฌํ•œ๋‹ค.
        //์ฆ‰ self.view.frame์˜ ์˜์—ญ๋งŒํผ context(์บ ๋ฒ„์Šค)๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์˜์—ญ๋งŒํผ ๋ Œ๋”๋ง(๊ทธ๋ฆฌ๊ธฐ)ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.
        
        let savedFrame = self.frame
        let savedScrollviewFrame = scrollView.frame
        let savedContentOffset = scrollView.contentOffset
        let scrollFrame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
        
        scrollView.contentOffset = .zero
        scrollView.frame = scrollFrame
        self.frame = scrollFrame
        
        //context๋Š” stack ๊ตฌ์กฐ
        //ํ•ด๋‹น ์˜ต์…˜์„ ๊ฐ€์ง„ Context๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑ
        //์บ ํผ์Šค ์ค€๋น„
        UIGraphicsBeginImageContextWithOptions(self.frame.size, true, 0.0)
        
        //๊ทธ๋ฆฌ๊ธฐ
        //์œ„์—์„œ ๋งŒ๋“  context์— ํ˜„์žฌ ๋ณด์—ฌ์ง€๊ณ  ์žˆ๋Š” ํ™”๋ฉด ๋ Œ๋”๋ง
        self.layer.render(in: UIGraphicsGetCurrentContext()!)

        //ํ˜„์žฌ ์ด๋ฏธ์ง€๊ฐ€ ๊ทธ๋ ค์ง„ context์—์„œ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
        
        scrollView.contentOffset = savedContentOffset
        scrollView.frame = savedScrollviewFrame
        self.frame = savedFrame

        //์บ ํผ์Šค ํ๊ธฐ
        //context pop
        UIGraphicsEndImageContext()

        return image
    }
}

 
 
 
+ iOS 17 ์ด์ƒ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•

[ iOS ] ํ™”๋ฉด ์บก์ณํ•˜๊ธฐ - UIGraphicsRenderer

๐Ÿ”ซ ๋ฌธ์ œ ๋ฐœ์ƒ iOS 17๊ฐ€ ๋ฐœํ‘œ๋˜๋ฉฐ, ์•ฑ์Šคํ† ์–ด์— ๋ฐฐํฌํ•œ ์„œ๋น„์Šค์— ํ˜น์‹œ deprecated๋œ ํด๋ž˜์Šค๋‚˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์„๊นŒ ์‹ถ์–ด ์ฐพ์•„๋ดค๋‹ค. ์ •๋ง ์•ˆํƒ€๊น๊ฒŒ๋„ '์ด๋ฏธ์ง€ ๊ณต์œ ํ•˜๊ธฐ' ๊ธฐ๋Šฅ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ UIGraphicsBeginImageCo

88yhtserof.tistory.com

 
 
 

728x90