Як перетворити UIView на зображення


94

Я хочу перетворити UIView на зображення та зберегти його у своєму додатку. Хтось може підказати мені, як зробити знімок екрана перегляду або перетворити його на зображення, і який найкращий спосіб зберегти його в програмі (не в рулоні камери)? Ось код для подання:

var overView   = UIView(frame: CGRectMake(0, 0, self.view.frame.width/1.3, self.view.frame.height/1.3))
overView.center = CGPointMake(CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds)-self.view.frame.height/16);
overView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(overView)
self.view.bringSubviewToFront(overView)

Також актуально: stackoverflow.com/q/59592933/294884
Fattie

Відповіді:


189

Розширення на UIViewмає зробити трюк.

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

Apple не рекомендує використовувати UIGraphicsBeginImageContextiOS 10 із введенням колірної гами P3. UIGraphicsBeginImageContextє sRGB і лише 32-розрядна версія. Вони представили новий UIGraphicsImageRendererAPI, який повністю управляється кольором, базується на блоках, має підкласи для PDF-файлів та зображень та автоматично керує життям контексту. Ознайомтесь із сесією 205 WWDC16, щоб отримати докладнішу інформацію (візуалізація зображень починається приблизно з позначки 11:50)

Щоб бути впевненим, що він працює на кожному пристрої, використовуйте #availableіз запасним варіантом попередніх версій iOS:

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContext(self.frame.size)
            self.layer.render(in:UIGraphicsGetCurrentContext()!)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return UIImage(cgImage: image!.cgImage!)
        }
    }
}

9
Це слід позначити як правильну відповідь. Усі інші параметри змусять відтворене зображення втратити чіткість і виглядати нерівним / розмитим.
TuplingD

Єдино правильний шлях! Для початківців використовується: let image = customView.asImage ()
Фабіо

1
Чи можна зробити UIView прозорим фоном? У мого є кілька закруглених кутів.
ixany

@ixany Це вже повинно про це подбати. Я щойно спробував із таким на дитячому майданчику, і це спрацювало: let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)); view.backgroundColor = .black; view.layer.cornerRadius = 9; view.asImage();
Naveed J.

2
Дякую! Якщо хтось знає, мені буде цікаво дізнатися, які відмінності існують у коді від hackingwithswift.com/example-code/media/… : return renderer.image {ctx in drawHierarchy (in: bounds, afterScreenUpdates: true)}
Kqtr

63

Ви можете використовувати розширення

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}

Ось швидка 3/4 версія:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}

5
Мені соромно бути тим, хто запитує це, але як я можу це назвати? У мене немає зображення та UIView, яким я хочу бути образом. Наведений вище код дає мені розширення UIImage ... але що тепер?
Dave G

8
let image = UIImage(view: myView)
doctorBroctor

Не працює для мене, оскільки UIGraphicsGetCurrentContext () може бути нульовим, а код виходить з ладу.
Хуан Карлос Оспіна Гонсалес

@JuanCarlosOspinaGonzalez Якщо UIGraphicsGetCurrentContext повернув нуль, тоді я підозрюю, що вам потрібно перевірити UIGraphicsBeginImageContext, щоб з'ясувати, чому це не вдалося. Я припускаю, що один із розмірів вашого розміру дорівнює нулю або іншим чином недійсний, але я навіть не уявляю, що може призвести до того, що це не вдасться.
Джон Стівен

2
ви також повинні пам’ятати про масштаб екрану, тому я б замінив перший рядок ініціалізатора таким: UIGraphicsBeginImageContextWithOptions(view.frame.size, false, UIScreen.main.scale)
iVentis

41

Перетворіть свій UIView на зображення за допомогою drawViewHierarchyInRect: afterScreenUpdates: що в рази швидше, ніж renderInContext

Важливе зауваження: не викликайте цю функцію з viewDidLoad або viewWillAppear , переконайтеся, що ви захоплюєте вигляд після того, як він буде відображений / завантажений повністю

Obj C

     UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.opaque, 0.0f);
     [myView drawViewHierarchyInRect:myView.bounds afterScreenUpdates:NO];
     UIImage *snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();

     myImageView.image =  snapshotImageFromMyView;

Збережіть відредаговане зображення Фотоальбом

     UIImageWriteToSavedPhotosAlbum(snapshotImageFromMyView, nil,nil, nil);

Стрім 3/4

    UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.isOpaque, 0.0)
    myView.drawHierarchy(in: myView.bounds, afterScreenUpdates: false)
    let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    print(snapshotImageFromMyView)
    myImageView.image = snapshotImageFromMyView

Супер легке узагальнення з розширенням, iOS11, swift3 / 4

extension UIImage{
    convenience init(view: UIView) {

    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    self.init(cgImage: (image?.cgImage)!)

  }
}


Use : 
 //myView is completly loaded/visible , calling this code after only after viewDidAppear is call
 imgVV.image = UIImage.init(view: myView)
 // Simple image object
 let img =  UIImage.init(view: myView)

3
Не працює у мене, я отримав чорний екран для мого UiImage
Бадр Філалі,

Справді ?? Чи можете ви вставити свій код сюди .. Можливо, ви використовуєте неправильний об'єкт UIView для конвертації як UIImage.
ViJay Avhad

`func convertViewIntoImg () -> Void {imgFakeCard.image = UIImage.init (view: card)}` З вашим розширенням в swift 3 ця функція викликається в viewDidLoad
Badr Filali

1
Дякуємо, що опублікували свій код і згадали, що ви телефонували в viewDidLoad. Ось що ви помилилися. Ви робите знімок виду перед тим, як він завантажується в пам’ять. Будь ласка, спробуйте зателефонувати коду в viewDidAppear, що точно вирішить проблему. На будь-яке питання, будь ласка, дайте відповідь.
ViJay Avhad

Це чудово, дякую, проблема виникла з того місця, де я зателефонував вашому подовжувачеві
Бадр Філалі

20

У iOS 10:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)
    }
}

1
це призводить до збоїв.
KingPolygon

18

Найкращі практики на iOS 10 та Swift 3

підтримуючи iOS 9 і раніше, все ще працює з iOS 13, Xcode 11.1, Swift 5.1

extension UIView {

    func asImage() -> UIImage? {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
            defer { UIGraphicsEndImageContext() }
            guard let currentContext = UIGraphicsGetCurrentContext() else {
                return nil
            }
            self.layer.render(in: currentContext)
            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

Я не впевнений, що означає питання під:

який найкращий спосіб зберегти його в програмі (не в рулоні камери)?


17
    UIGraphicsBeginImageContext(self.view.bounds.size);        
    self.view.layer.renderInContext(UIGraphicsGetCurrentContext())
    var screenShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


1
UIGraphicsGetImageFromCurrentImageContext повертає UIImage. Не потрібно передавати його UIImage
Лео Дабус

Це робить скріншот усього вигляду, так? Я просто хочу перетворити UIView, тобто огляд у своєму коді, який є підвидом self.view на зображення. Не весь вигляд!
Sameer Hussain

@Vakas, коли я намагався надрукувати image.it відображається занадто малим. Що є рішенням для цього
Krutarth Patel

6
Знімок скріншоту не має хорошої якості, що мені робити?
Ніла

13

Наприклад, якщо я маю вигляд розміру: 50 50 на 100 100. Я можу використовувати наступне, щоб зробити знімок екрана:

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0);
    self.view.drawViewHierarchyInRect(CGRectMake(-50,-5-,view.bounds.size.width,view.bounds.size.height), afterScreenUpdates: true)
    var image:UIImage = UIGraphicsGetImageFromCurrentImageContext();

у рядку self.view є помилка в параметрі. я вважаю, що це має бути -5, а не -5-
Круртарт Патель

@sameer у мене одне питання. коли я друкую це зображення, воно замале
Krutarth Patel

7

На мій погляд, підхід до ініціалізатора не такий вже й чудовий, оскільки він створює два зображення.

Я віддаю перевагу цьому:

extension UIView {
    var snapshot: UIImage? {
        UIGraphicsBeginImageContext(self.frame.size)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

Ви повинні називати властивість інакше, ніж зображення. Це може заважати підкласам, які мають властивості, що називаються зображеннями, наприклад UIImageView.
Гоббс Тіге

Виконав пропозицію @HobbestheTige і перейменував власність
Тіно

6

Свіфт 4.2, iOS 10

extension UIView {

    // If Swift version is lower than 4.2, 
    // You should change the name. (ex. var renderedImage: UIImage?)

    var image: UIImage? {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in layer.render(in: rendererContext.cgContext) }
    }
}

Зразок

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .blue

let view2 = UIView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
view2.backgroundColor = .red
view.addSubview(view2)

let imageView = UIImageView(image: view.image)

введіть тут опис зображення


Просто хотів зазначити, що це потрібно назвати інакше, якщо ви використовуєте будь-який UIImageViews у своєму проекті, інакше .image стає лише для читання. Можливо, назвіть це "screenCapture" замість "image".
Кріс

@Chris Так, ти маєш рацію. Вам слід змінити ім'я, якщо версія Swift нижче 4.2. Дякуємо, що вказали на помилку.
Den

6

Мені це підходить для Xcode 9 / Swift 3.2 / Swift 4 та Xcode 8 / Swift 3

 if #available(iOS 10.0, *) {

     // for Xcode 9/Swift 3.2/Swift 4 -Paul Hudson's code
     let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
     let capturedImage = renderer.image { 
         (ctx) in
         view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
     }
     return capturedImage

 } else {

     // for Xcode 8/Swift 3
     UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
     view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
     let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
     UIGraphicsEndImageContext()
     return capturedImage!
 }

Ось як використовувати його всередині функції:

fileprivate func captureUIImageFromUIView(_ view:UIView?) -> UIImage {

     guard (view != nil) else{

        // if the view is nil (it's happened to me) return an alternative image
        let errorImage = UIImage(named: "Error Image")
        return errorImage
     }

     // if the view is all good then convert the image inside the view to a uiimage
     if #available(iOS 10.0, *) {

         let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
         let capturedImage = renderer.image { 
             (ctx) in
             view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
         }
         return capturedImage

     } else {

         UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
         view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
         let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
         UIGraphicsEndImageContext()
         return capturedImage!
     }
}

Ось як зробити щось із зображенням, повернутим із функції:

@IBOutlet weak fileprivate var myCustomView: UIView!
var myPic: UIImage?

let myImageView = UIImageView()

@IBAction fileprivate func saveImageButtonTapped(_ sender: UIButton) {

   myPic = captureUIImageFromUIView(myCustomView)

   // display the pic inside a UIImageView
   myImageView.image = myPic!
}

Я отримав відповідь Xcode 9 / Swift 3.2 / Swift 4 від Пола Хадсона перетворити uiview на uiimage

Я давно отримав Xcode 8 / Swift 3 десь на SO, і я забув, де :(


Також де зображення відображається та / або зберігається?
Famic Tech

@FamicTech як тільки образ створюється, молодь вирішує, що з ним робити. У прикладі з мого коду я щойно перетворив UIView на UIImage з ім'ям "myPic", але я нічого не зробив із "myPic". Наступним можливим кроком є ​​відображення 'myPic' всередині UIImageVIew таким чином: myImageVIew.image = myPic. Якщо ви використовуєте collectionView і хотіли відобразити в ньому зображення, тоді ви створюєте UIImageVIew у кожній комірці, а потім відображаєте зображення (myPic) усередині UIImageVIew через indexPath.item з джерела даних ex: myImageView.image = dataSource [indexPath .item] .myPic
Lance Samaria

@FamicTech Ви робите невелику помилку. Це не має нічого спільного з collectionVIew. У вас є два різні типи класів: UIImage та UIView. Обидва вони можуть бути використані для відображення будь-якого зображення, але обидва вони роблять це по-різному. Практично все в iOS - це підклас UIView, але UIImage може відображати лише зображення (2 різні речі). Ця функція робить так, щоб взяти UIVIew і перетворити його на UIImage. Простіше, наприклад. Якщо ви хочете перетворити 4.0 (Double) на 4 (Int), ви перетворюєте Int (4.0) на результат 4. Але за допомогою цього ви перетворюєте зображення всередині UIVIew на UIImage. Розумієте?
Lance Samaria

що я намагаюся зробити, це зробити так, щоб користувач підійшов до мого контролера перегляду колекції та натиснув кнопку, яка повинна взяти поточний вигляд, який, як видається, є переглядом колекції 10x10 і створити зображення всієї сітки 10x10 (зверніть увагу, що весь сітка не повністю видно в полі зору). Чи вирішує ваш код вище цей випадок використання?
Famic Tech

Кнопка знаходиться в контролері навігації. Користувач натискає його, і він повинен "взяти" подання колекції, що відображається під навігаційним контролером, і створити зображення подання колекції (PDF також також буде працювати) з усією інформацією в клітинках у поданні колекції.
Famic Tech

5
var snapshot = overView.snapshotViewAfterScreenUpdates(false)

або в об’єкт-с

UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];

Чи є imageView таким самим, як image? Якщо так, то як я можу зберегти його у своєму додатку?
Sameer Hussain

4

Ви можете легко використовувати його, використовуючи розширення, як це

// Take a snapshot from a view (just one view)
let viewSnapshot = myView.snapshot

// Take a screenshot (with every views in screen)
let screenSnapshot = UIApplication.shared.snapshot

// Take a snapshot from UIImage initialization
UIImage(view: self.view)

Якщо ви хочете використовувати ці методи / змінні розширення, реалізуйте це

  1. Розширення UIImage

    extension UIImage {
        convenience init(view: UIView) {
            if let cgImage = view.snapshot?.cgImage {
                self.init(cgImage: cgImage)
            } else {
                self.init()
            }
        }
    }
  2. Розширення UIView

    extension UIView {
    
        var snapshot: UIImage? {
            UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
            if UIGraphicsGetCurrentContext() != nil {
                drawHierarchy(in: bounds, afterScreenUpdates: true)
                let screenshot = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return screenshot
            }
            return nil
        }
    }
  3. Розширення програми UIA

    extension UIApplication {
    
        var snapshot: UIImage? {
            return keyWindow?.rootViewController?.view.snapshot
        }
    }

я мав подання із маніпульованими підпозиціями zPositions, і інші відповіді не дали мені правильних позицій шару, дякую.
Ашкан Годрат,

3

або iOS 10+, ви можете використовувати новий UIGraphicsImageRenderer + рекомендовану схему drawHierarchy, яка в деяких ситуаціях може бути набагато швидшою, ніж layer.renderInContext

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
        return renderer.image { _ in
            self.drawHierarchy(in: CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height), afterScreenUpdates: false)
        }
    }
}

3

Дякую @Bao Tuan Diep! Я хочу додати додаток.

Коли ви використовуєте код:

yourView.layer.render(in:UIGraphicsGetCurrentContext()!)

Ви повинні помітити, що:

 - If you had used `autoLayout` or `Masonry` in `yourView` (that you want to convert) .
 - If you did not add `yourView` to another view which means that `yourView` was not used as a subview but just an object.

Потім ви повинні використовувати :

[yourView setNeedsLayout];
[yourView layoutIfNeeded];

оновити yourViewраніше yourView.layer.render(in:UIGraphicsGetCurrentContext()!).

В іншому випадку ви можете отримати об’єкт зображення, який не містить елементів


Дякую за це. Дуже допомогло !!
Максим Князєв

2

Свіфт 4.2

import Foundation
import UIKit  

extension UIImage {

    convenience init(view: UIView) {

        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)

    } 
}

за допомогою:

нехай img = UIImage.init (перегляд: self.holderView)


1

Реалізація в Swift 3 :

Додайте код нижче, поза межами класу.

extension UIImage {
    convenience init(_ view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)
    }
}

Використання:

let image = UIImage( Your_View_Outlet )

отримання помилки "Накреслення подання (0x1040a6970, UIView), яке не було надано хоча б один раз"
kishu mewara

0

Я реалізував метод @Naveed J. таким чином, і він спрацював як шарм.

Ось його екстенсія:

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

Ось як я це реалізував.

//create an image from yourView to display
//determine the frame of the view/imageimage
let screen = self.superview!.bounds
let width = screen.width / 4 //make image 1/4 width of screen
let height = width
let frame = CGRect(x: 0, y: 0, width: width, height: height)
let x = (screen.size.width - frame.size.width) * 0.5
let y = (screen.size.height - frame.size.height) * 0.5
let mainFrame = CGRect(x: x, y: y, width: frame.size.width, height: frame.size.height)

let yourView = YourView() //instantiate yourView
yourView.frame = mainFrame //give it the frame
yourView.setNeedsDisplay() //tell it to display (I am not 100% sure this is needed)

let characterViewImage = yourView.asImage()

0

Ініціалізатор з новим UIGraphicsImageRenderer, доступним з iOS 10:

extension UIImage{
    convenience init(view: UIView) {

    let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
    let canvas = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
    let image = renderer.image { _ in
        self.drawHierarchy(in: canvas, afterScreenUpdates: false)
    }
    self.init(cgImage: (image?.cgImage)!)
  }
}

0

добре працюй зі мною!

Стрімкий4

 extension UIView {

    func toImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
        let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return snapshotImageFromMyView!
    }

    }

return snapshotImageFromMyView!Тема 1: Фатальна помилка: Несподівано знайдено нуль під час розгортання необов’язкового значення
Такасур

0

Оскільки подання містить розмитий підпрогляд (наприклад, екземпляр UIVisualEffectView ), працює лише drawViewHierarchyInRect: afterScreenUpdates .

Відповідь @ViJay Avhad для цього випадку правильна.


0

Використання UIGraphicsImageRenderer не працює, коли подання містить підпрогляди Scene Kit, металеві або Sprite Kit. У цих випадках використовують

let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let image = renderer.image { ctx in
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}

-1
please try below code.
-(UIImage *)getMainImageFromContext
{
UIGraphicsBeginImageContextWithOptions(viewBG.bounds.size, viewBG.opaque, 0.0);
[viewBG.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.