Как преобразовать UIView в изображение


99

Я хочу преобразовать 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

Ответы:


199

Расширение 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 не рекомендует UIGraphicsBeginImageContextзапускать iOS 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 07

Единственно верный способ! Для новичков используется: 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();
Навид Дж.

2
Спасибо! Если кто-нибудь знает, мне было бы любопытно узнать, какие различия есть с кодом из hackingwithswift.com/example-code/media/… : return renderer.image {ctx in drawHierarchy (in: bounds, afterScreenUpdates: true)}
Kqtr

64

вы можете использовать расширение

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 06

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);

Swift 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
Бадр Филали

В самом деле ?? Можете ли вы вставить сюда свой код? Возможно, U использует неправильный объект UIView для преобразования в UIImage.
ViJay Avhad

`func convertViewIntoImg () -> Void {imgFakeCard.image = UIImage.init (view: card)}` С вашим расширением в swift 3 эта функция вызывается в viewDidLoad
Бадр Филали

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 в изображение. Не весь вид!
Самир Хуссейн,

@Vakas, когда я попытался напечатать изображение. Он отображается слишком маленьким. Какое решение для этого?
Крутарт Патель

6
Изображение скриншота не хорошего качества, что делать?
Neela

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, у меня есть один вопрос. Когда я распечатываю это изображение, оно слишком маленькое
Крутарт Патель,

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.
Hobbes the

Вслед предложение @HobbestheTige «s и переименован в собственность
Тино

6

Swift 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)

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


Просто хотел указать, что это нужно назвать как-то еще, если вы используете какие-либо UIImageView в своем проекте, иначе .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 откуда-то на ТАК давным-давно, и я забыл где :(


Также, где изображение отображается и / или сохраняется?
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
Лэнс Самария

@FamicTech Вы делаете небольшую ошибку. Это не имеет ничего общего с collectionVIew. У вас есть 2 разных типа классов: 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)

или в objective-c

UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];

ImageView то же самое, что и изображение? Если да, то как мне сохранить его в своем приложении?
Самир Хуссейн,

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. Расширение UIApplication

    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

Swift 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), который не был визуализирован хотя бы один раз, требуется»
Кишу Мевара

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

у меня отлично работает!

Swift4

 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: Неустранимая ошибка: Неожиданно обнаружено nil при развертывании необязательного значения
Такасур

0

Поскольку представление содержит размытое представление (например, экземпляр UIVisualEffectView ), работает только drawViewHierarchyInRect: afterScreenUpdates .

Ответ @ViJay Avhad верен для этого случая.


0

Использование UIGraphicsImageRenderer не работает, если представление содержит подвиды Scene Kit, Metal или 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.