Transform UIView to UIImage and set it with UIImageView - ios

I refered to this question, and did add extension to UIView:
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!)
}
}
}
Then i create very simple test view:
private class FakeTestView: BaseView {
override func prepare() {
backgroundColor = .blue
setup()
}
private func setup(){
let lbl = LabelSL.regular()
lbl.text = "LabelSL.regular()"
addSubview(lbl)
lbl.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
lbl.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
That view showing correctly when treated as UIView.
Finally, i tried:
let newSlideFrame = CGRect(x: CGFloat(i) * event.expectedWidth(),
y: 0,
width: event.expectedWidth(),
height: frame.size.height)
let imgView = UIImageView()
imgView.contentMode = .scaleAspectFit
imgView.frame = newSlideFrame
imgView.image = FakeTestView().asImage()
scroll.addSubview(imgView)
But there is nothing showing. Code from above work when i try to add UIView, or UIImageView with sample images.

Related

Get visible part of UIImageView as UIImage in Swift

In my app, I'm trying to implement a crop feature. So now I need to convert UIImageView's visible part to UIImage.
Here's a screenshot from the UI debugger.
I've tried to achieve it with this function, but it doesn't work.
func imageFromImageView(imageView: UIImageView) -> UIImage? {
UIGraphicsBeginImageContext(imageView.frame.size)
let context = UIGraphicsGetCurrentContext()!
context.rotate(by: 2 * .pi)
imageView.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
You can use bellow extension:
private var rendererKey: UInt8 = 0
extension UIView {
var renderer: UIGraphicsImageRenderer! {
get {
guard let rendererInstance = objc_getAssociatedObject(self, &rendererKey) as? UIGraphicsImageRenderer else {
self.renderer = UIGraphicsImageRenderer(bounds: bounds)
return self.renderer
}
return rendererInstance
}
set(newValue) {
objc_setAssociatedObject(self, &rendererKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
func snapImageView() -> UIImageView {
let img:UIImage = renderer.image { ctx in
DispatchQueue.main.async {
layer.render(in: ctx.cgContext)
}
}
let imageView:UIImageView = UIImageView(image: img)
imageView.frame = renderer.format.bounds
imageView.clipsToBounds = true
return imageView
}
}
// Generate image and image view of any view instance
let anImageView = yourView.snapImageView()

Why does UIGraphicsGetCurrentContext return nil after UIGraphicsBeginImageContext

I am following a code example to make a blurred UILabel, https://stackoverflow.com/a/62224908/2226315.
My requirement is to make the label on blur after label initialization instead of calling the blur method at runtime. However, when I try to call blur after label gets initialized the value returned from UIGraphicsGetCurrentContext is nil hence having a "Fatal error: Unexpectedly found nil while unwrapping an Optional value"
UIGraphicsBeginImageContext(bounds.size)
print("DEBUG: bounds.size", bounds.size)
self.layer.render(in: UIGraphicsGetCurrentContext()!) // <- return nil
var image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
print("DEBUG: image image", image)
I tried adding the code in all the following places individually, the context can be fetched now however it does not generate the blur effect as expected.
override func layoutSubviews() {
super.layoutSubviews()
self.blur()
}
// OR
override func draw(_ rect: CGRect) {
super.draw(rect)
self.blur()
}
Full code snippet,
class BlurredLabel: UILabel {
func blur(_ blurRadius: Double = 2.5) {
let blurredImage = getBlurryImage(blurRadius)
let blurredImageView = UIImageView(image: blurredImage)
blurredImageView.translatesAutoresizingMaskIntoConstraints = false
blurredImageView.tag = 100
blurredImageView.contentMode = .center
blurredImageView.backgroundColor = .white
addSubview(blurredImageView)
NSLayoutConstraint.activate([
blurredImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
blurredImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
func unblur() {
subviews.forEach { subview in
if subview.tag == 100 {
subview.removeFromSuperview()
}
}
}
private func getBlurryImage(_ blurRadius: Double = 2.5) -> UIImage? {
UIGraphicsBeginImageContext(bounds.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
guard let image = UIGraphicsGetImageFromCurrentImageContext(),
let blurFilter = CIFilter(name: "CIGaussianBlur") else {
UIGraphicsEndImageContext()
return nil
}
UIGraphicsEndImageContext()
blurFilter.setDefaults()
blurFilter.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)
var convertedImage: UIImage?
let context = CIContext(options: nil)
if let blurOutputImage = blurFilter.outputImage,
let cgImage = context.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
convertedImage = UIImage(cgImage: cgImage)
}
return convertedImage
}
}
REFERENCE
Add blur view to label?
How to blur UILabel text
UPDATE
Usage based on "Eugene Dudnyk" answer
definitionLabel = BlurredLabel()
definitionLabel.numberOfLines = 0
definitionLabel.lineBreakMode = .byWordWrapping
definitionLabel.textColor = UIColor(named: "text")
definitionLabel.text = "Lorem Ipsum is simply dummy text"
definitionLabel.clipsToBounds = false
definitionLabel.isBluring = true
Here is a better solution - instead of retrieving the blurred image, just let the label blur itself.
When you need it to be blurred, set label.isBlurring = true.
Also, this solution is better for performance, because it reuses the same context and does not need the image view.
class BlurredLabel: UILabel {
var isBlurring = false {
didSet {
setNeedsDisplay()
}
}
var blurRadius: Double = 2.5 {
didSet {
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
}
}
lazy var blurFilter: CIFilter? = {
let blurFilter = CIFilter(name: "CIGaussianBlur")
blurFilter?.setDefaults()
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
return blurFilter
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.isOpaque = false
layer.needsDisplayOnBoundsChange = true
layer.contentsScale = UIScreen.main.scale
layer.contentsGravity = .center
isOpaque = false
isUserInteractionEnabled = false
contentMode = .redraw
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func display(_ layer: CALayer) {
let bounds = layer.bounds
guard !bounds.isEmpty && bounds.size.width < CGFloat(UINT16_MAX) else {
layer.contents = nil
return
}
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, layer.contentsScale)
if let ctx = UIGraphicsGetCurrentContext() {
self.layer.draw(in: ctx)
var image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
if isBlurring, let cgImage = image {
blurFilter?.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
let ciContext = CIContext(cgContext: ctx, options: nil)
if let blurOutputImage = blurFilter?.outputImage,
let cgImage = ciContext.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
image = cgImage
}
}
layer.contents = image
}
UIGraphicsEndImageContext()
}
}
Transformed #EugeneDudnyk answer to UIView extension so it can be used also with TextView.
extension UIView {
struct BlurableKey {
static var blurable = "blurable"
}
func blur(radius: CGFloat) {
guard superview != nil else { return }
UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard
let blur = CIFilter(name: "CIGaussianBlur"),
let image = image
else {
return
}
blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blur.setValue(radius, forKey: kCIInputRadiusKey)
let ciContext = CIContext(options: nil)
let boundingRect = CGRect(
x:0,
y: 0,
width: frame.width,
height: frame.height
)
guard
let result = blur.value(forKey: kCIOutputImageKey) as? CIImage,
let cgImage = ciContext.createCGImage(result, from: boundingRect)
else {
return
}
let blurOverlay = UIImageView()
blurOverlay.frame = boundingRect
blurOverlay.image = UIImage(cgImage: cgImage)
blurOverlay.contentMode = .left
addSubview(blurOverlay)
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
blurOverlay,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
func unBlur() {
guard
let blurOverlay = objc_getAssociatedObject(self, &BlurableKey.blurable) as? UIImageView
else {
return
}
blurOverlay.removeFromSuperview()
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
nil,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
var isBlurred: Bool {
return objc_getAssociatedObject(self, &BlurableKey.blurable) is UIImageView
}
}

Custom thumb image of UISlider doesn't show full of image icon size

I'm making a custom UISlider with thumb like this:
But here is what I get:
I want my thumb show with full height of the grey container, my image has size 160x160 for #2x. But when set into UISlider it doesn't act like that. Here is the view debugging:
I'm use a custom class for this UISlider, where's wrong in the code?:
class CustomSlider: UISlider {
#IBInspectable var trackHeight: CGFloat = 0.0001
// #IBInspectable var thumbRadius: CGFloat = 20
// Custom thumb view which will be converted to UIImage
// and set as thumb. You can customize it's colors, border, etc.
private lazy var thumbView: UIView = {
let thumb = UIImageView()
// thumb.backgroundColor = .white
// thumb.layer.borderWidth = 0.4
// thumb.layer.borderColor = UIColor.white.cgColor
thumb.image = UIImage(named: "icon_slider")
return thumb
}()
override func awakeFromNib() {
super.awakeFromNib()
let thumb = thumbImage()
setThumbImage(thumb, for: .normal)
}
private func thumbImage() -> UIImage {
thumbView.frame = CGRect(x: 0, y: 50/2, width: 50, height: 50)
// thumbView.dropShadow(color: .red, opacity: 1, offSet: CGSize(width: -1, height: 1), radius: 3, scale: true)
thumbView.layer.cornerRadius = 4
thumbView.layer.masksToBounds = true
// Convert thumbView to UIImage
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds)
return renderer.image { rendererContext in
thumbView.layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(thumbView.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
// Set custom track height
// As seen here: https://stackoverflow.com/a/49428606/7235585
var newRect = super.trackRect(forBounds: bounds)
newRect.size.height = trackHeight
return newRect
}
}

How do I screenshot a uiview without adding it to the subview?

I have multiple subviews on a parent view, and I need to convert the uiview to a uiimage but of only certain subviews. So I added a tag to the views I needed to take a screenshot of and added it to its own view, but when I try to screenshot that I get a black screen. However, when I use the regular parent view I get a photo with all the subviews.
let viewPic = UIView()
for subview in self.view.subviews {
if(subview.tag == 6) {
viewPic.addSubview(subview)
}
if(subview.tag == 8) {
viewPic.addSubview(subview)
}
}
let picImage = viewPic.getSnapshotImage() //This is a black screen
getSnapshotImage
extension UIView {
public func getSnapshotImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0)
self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
let snapshotItem: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return snapshotItem
}
}
First of all, your viewPic doesn't set the frame size so the default will be zero frame which may cause the issue.
Secondly, I tried to use your getSanpshotImage() on my sample project, but I always get the blank image.
Have a look at the demo code, I can get what you want (see the screenshot):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let viewPic = UIView()
viewPic.frame = self.view.frame
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view1.backgroundColor = UIColor.red
viewPic.addSubview(view1)
let view2 = UIView()
view2.frame = CGRect(x: 0, y: 200, width: 100, height: 100)
view2.backgroundColor = UIColor.blue
viewPic.addSubview(view2)
let picImage = viewPic.convertToImage() //This is a black screen
print(picImage)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension UIView {
func convertToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}

How can I change the color of a UIImage? [duplicate]

This question already has answers here:
iPhone - How do you color an image?
(10 answers)
Closed 2 months ago.
I want not to change the backgroundColor of an UIImage, but rather to change the color of the whole image.
But the problem is: I can only change the backgroundColor.
The accepted answer is correct, but there is a much more easy way for UIImageView:
Obj-C:
UIImage *image = [UIImage imageNamed:#"foo.png"];
theImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[theImageView setTintColor:[UIColor redColor]];
Swift 2:
let theImageView = UIImageView(image: UIImage(named:"foo")!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate))
theImageView.tintColor = UIColor.redColor()
Swift 3:
let theImageView = UIImageView(image: UIImage(named:"foo")!.withRenderingMode(.alwaysTemplate))
theImageView.tintColor = UIColor.red
UIImage *image = [UIImage imageNamed:#"triangle.png"];
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToMask(context, rect, image.CGImage);
CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextFillRect(context, rect);
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *flippedImage = [UIImage imageWithCGImage:img.CGImage
scale:1.0 orientation: UIImageOrientationDownMirrored];
yourUIImageView.image = flippedImage;
For swift 3.2 and 4
extension UIImageView{
func changePngColorTo(color: UIColor){
guard let image = self.image else {return}
self.image = image.withRenderingMode(.alwaysTemplate)
self.tintColor = color
}
}
Use :
self.yourImageView.changePngColorTo(color: .red)
Details
Xcode 10.2.1 (10E1001), Swift 5
Way 1. Extension UIImage
source: https://stackoverflow.com/a/40177870/4488252
extension UIImage {
convenience init?(imageName: String) {
self.init(named: imageName)
accessibilityIdentifier = imageName
}
// https://stackoverflow.com/a/40177870/4488252
func imageWithColor (newColor: UIColor?) -> UIImage? {
if let newColor = newColor {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()!
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setBlendMode(.normal)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
context.clip(to: rect, mask: cgImage!)
newColor.setFill()
context.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
newImage.accessibilityIdentifier = accessibilityIdentifier
return newImage
}
if let accessibilityIdentifier = accessibilityIdentifier {
return UIImage(imageName: accessibilityIdentifier)
}
return self
}
}
Usage
let imageView = UIImageView(frame: CGRect(x: 40, y: 250, width: 40, height: 40))
view.addSubview(imageView)
// Set color
imageView.image = UIImage(imageName: "Apple")?.imageWithColor(newColor: UIColor.blue)
// Reset color
//imageView.image = imageView.image?.imageWithColor(newColor: nil)
Way 2. Extension UIImageView
extension UIImageView {
var imageColor: UIColor? {
set (newValue) {
guard let image = image else { return }
if newValue != nil {
self.image = image.withRenderingMode(.alwaysTemplate)
tintColor = newValue
} else {
self.image = image.withRenderingMode(.alwaysOriginal)
tintColor = UIColor.clear
}
}
get { return tintColor }
}
}
Usage
let imageView = UIImageView(frame: CGRect(x: 40, y: 250, width: 40, height: 40))
view.addSubview(imageView)
imageView.image = UIImage(imageName: "Apple")
// Set color
imageView.imageColor = UIColor.green
// Reset color
//imageView.imageColor = nil
Full sample
Do not forget to paste here all code which placed above
import UIKit
class ImageView: UIImageView {
enum ImageColorTransformType {
case none, imageExtension, imageViewExtension
}
var imageColorTransformType = ImageColorTransformType.none
}
class ViewController: UIViewController {
weak var colorSwitchButton: UIBarButtonItem?
private var imageViews = [ImageView]()
private var appleImage: UIImage { return UIImage(imageName: "apple")! }
private var redAppleImage: UIImage { return UIImage(imageName: "red_apple")! }
override func viewDidLoad() {
super.viewDidLoad()
createNewImageView(x: 40, y:100, image:appleImage, imageColorTransformType: .none)
createNewImageView(x: 100, y:100, image:appleImage, imageColorTransformType: .imageExtension)
createNewImageView(x: 160, y:100, image:appleImage, imageColorTransformType: .imageViewExtension)
createNewImageView(x: 40, y:160, image:redAppleImage, imageColorTransformType: .none)
createNewImageView(x: 100, y:160, image:redAppleImage, imageColorTransformType: .imageExtension)
createNewImageView(x: 160, y:160, image:redAppleImage, imageColorTransformType: .imageViewExtension)
let buttonItem = UIBarButtonItem(title: "switch", style: .plain, target: self,
action: #selector(colorSwitchButtonTouchedUpInside(_:)))
navigationItem.rightBarButtonItem = buttonItem
colorSwitchButton = buttonItem
useOriginalColors = false
}
private func createNewImageView(x:CGFloat, y: CGFloat, image: UIImage, imageColorTransformType: ImageView.ImageColorTransformType) {
let imageView = ImageView(frame: CGRect(x: x, y: y, width: 40, height: 40))
imageView.image = image
imageView.imageColorTransformType = imageColorTransformType
imageViews.append(imageView)
view.addSubview(imageView)
}
private var _useOriginalColors = false
private var useOriginalColors: Bool {
set(value) {
_useOriginalColors = value
if (value) {
navigationItem.title = "Original colors"
for imageView in imageViews {
switch imageView.imageColorTransformType {
case .imageExtension: imageView.image = imageView.image?.imageWithColor(newColor: nil)
case .imageViewExtension: imageView.imageColor = nil
case .none: break
}
}
} else {
navigationItem.title = "Template colors"
for imageView in imageViews {
switch imageView.imageColorTransformType {
case .imageExtension: imageView.image = imageView.image?.imageWithColor(newColor: UIColor.blue)
case .imageViewExtension: imageView.imageColor = UIColor.green
case .none: break
}
}
}
}
get { return _useOriginalColors }
}
#objc func colorSwitchButtonTouchedUpInside(_ sender: Any) { useOriginalColors = !useOriginalColors }
}
Storyboard
Result
You can also do this in swift with the following code:
// language: Swift
let tintedImage = UIImageView(image: UIImage(named:"whatever")!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate))
tintedImage.tintColor = UIColor.redColor()
Swift + Interface builder (storyboard)
If you added the UIImageView in the interface builder:
myIcon.image = myIcon.image!.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
myIcon.tintColor = UIColor.red // your color
where myIcon is an outlet from your storyboard, ex: #IBOutlet weak var myIcon: UIImageView!
Change the color of the image in UIImageView:
public extension UIImageView {
func tintImage(color: UIColor) {
image = image?.tint(color: color)
}
}
Just call the method:
imageView.tintImage(color: .red)
To add to skywinder's answer
You can also do this on a button:
UIImage *image = [UIImage imageNamed:#"foo.png"];
[myButton setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
[myButton setTintColor:[UIColor redColor]];

Resources