UITextView lags scrolling with added images - ios

I've a UITextView in which I add images to, with ImagePickerController.
If there is only text in it, it is smooth, but after adding 3 or 4 images it becomes laggy on scroll within its content.
I compress the image when adding it, to it's lowest quality, but I must be doing something wrong because it still lags after the third image.
Here's the code:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
guard let _image = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
self.dismiss(animated: true, completion: nil)
// Compress the retrieved image
let compressedImage = UIImage(data: _image.lowestQualityJPEGNSData!)
//create and NSTextAttachment and add your image to it.
let attachment = NSTextAttachment()
let oldWidth = compressedImage?.size.width // scales it within the UITextView
let scaleFactor = oldWidth! / (bodyEditText.frame.size.width - 10); // adjusts the desired padding
attachment.image = UIImage(cgImage: (compressedImage?.cgImage!)!, scale: scaleFactor, orientation: .up)
//put your NSTextAttachment into and attributedString
let attString = NSAttributedString(attachment: attachment)
//add this attributed string to the current position.
bodyEditText.textStorage.insert(attString, at: bodyEditText.selectedRange.location)
}
extension UIImage {
var uncompressedPNGData: Data? { return UIImagePNGRepresentation(self) }
var highestQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 1.0) }
var highQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.75) }
var mediumQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.5) }
var lowQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.25) }
var lowestQualityJPEGNSData:Data? { return UIImageJPEGRepresentation(self, 0.0) }
}
What may be causing this issue and any tip how can I overpass this?
Thanks for helping
EDIT
Thanks to Duncan C I've managed to remove the whole lag and increase the performance rendering images A LOT.
By that I've replaced my imagePickerController with the following content:
let size = __CGSizeApplyAffineTransform(_image.size, CGAffineTransform.init(scaleX: 0.3, y: 0.3))
let hasAlpha = false
let scale: CGFloat = 0.0 // Automatically use scale factor of main screen
UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
_image.draw(in: CGRect(origin: CGPoint.zero, size: size))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let att = NSTextAttachment()
att.image = scaledImage
//put your NSTextAttachment into and attributedString
let attString = NSAttributedString(attachment: att)
//add this attributed string to the current position.
bodyEditText.textStorage.insert(attString, at: bodyEditText.selectedRange.location)

You're misusing the scale factor. That's intended for handling the 1x, 2x, and 3x scaling of normal, retina, and #3x retina images.
The way you're doing it, the system has to render the full-sized images into your bounds rectangles each time it wants to draw one. If the images are considerably bigger than the size you're displaying them, you waste a bunch of memory and processing time.
First try simply setting your image view's contentMode to .scaleAspectFit and install the original image in image view without mucking around with the scale factor. See how that performs.
If that doesn't give acceptable performance you should render your starting images into an off-screen graphics context that's the target size of your image, and install the re-rendered image into your image views. You can use UIGraphicsBeginImageContextWithOptions() or various other techniques to resize your images for display.
Take a look at this link for information on image scaling and resizing:
http://nshipster.com/image-resizing/

Its not the best practice to do that. UITextViews is meant for text not all views.
you need to make a custom UIView consisting of a 1, textview 2, UICollectionView for numerous images.
In this way you can reuse this custom view at numerous places too throughout the project

Related

Quality get reduced when convert imageview to image

In photo editor screen , I have imageview and it has background image and on top of imageview I add elements like text (label), stickers(images) etc. , Now for the final image containing all elements added on imageview , I am getting image from below code
clipRect is rect for background image inside imageview, image is aspectfit in imageview
Below is code inside UIView extension which has function to generate image out of view.
self == uiview
let op_format = UIGraphicsImageRendererFormat()
op_format.scale = 1.0
let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: clipRect!.origin, size: outputSize), format: op_format)
let originalBound = self.bounds
self.bounds = CGRect(origin: clipRect!.origin, size: clipRect!.size)
var finalImage = renderer.image { ctx in
self.drawHierarchy(in: CGRect(origin: self.bounds.origin, size: outputSize), afterScreenUpdates: true)
}
self.bounds = CGRect(origin: originalBound.origin, size: originalBound.size)
Issue here is quality of final image quality is very poor as compared to original background image.
Don't set the scale of your UIGraphicsImageRendererFormat to 1. That forces the output image to #1x (non-retina). For most (all?) iOS devices, that will cause a 2X or 3X loss of resolution. Leave the scale value of the UIGraphicsImageRendererFormat at the default value.
you can take screenshot as well
// Convert a uiview to uiimage
func captureView() -> UIImage {
// Gradually increase the number for high resolution.
let scale = 1.0
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, scale)
layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image:UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}

losing image quality while converting base 64

I have one pdf and I am converting pdf to image and displaying in UIImageView and then after I am converting that image to base 64 but while converting base 64 I am losing the image quality so is there any way to prevent from losing quality while converting to base 64
please tell me is there any solution for that
here is my code for convert
let previewImage1 = convertedImageView.getImage()
let btnImg = UIButton()
btnImg.setImage(previewImage1, for: .normal)
let btn1Imggg2 = btnImg.image(for: .normal)
let imageData2 = btnImg?.jpegData(compressionQuality: 0.0)
let imgString2 = imageData2!.base64EncodedString(options: .init(rawValue: 0))
Even if i set compress quality to 0.0 still its compressing image
i have UIView Inside that i have ScrollView inside that i have ImageView and i am converting whole UIView as image and then convert into base64 the is the scenario hope this help you to understand
Code For UIView To UIImage
func getImage(scale: CGFloat? = nil) -> UIImage {
let newScale = scale ?? UIScreen.main.scale
self.scale(by: newScale)
let format = UIGraphicsImageRendererFormat()
format.scale = newScale
let renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: format)
let image = renderer.image { rendererContext in
self.layer.render(in: rendererContext.cgContext)
}
return image
}
By doing this
let imageData2 = btnImg?.jpegData(compressionQuality: 0.0)
you are compressing the image with the maximum value possible. Change this code to
let imageData2 = btnImg?.jpegData(compressionQuality: 1.0)
and you will be good to go.
As you are converting the UIView to UIImage below function can be useful to you :
func image(with view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
If the jpeg data does not suffice your requirement the user pngData like this
let imageData2 = btnImg?.pngData()
After getting the conversion to Image apply the base64 conversion as you are doing right now.
Okay, since you have a nested hierarchy (UIView -> UIScrollView -> UIImageView) and you wan't to capture that whole hierarchy as an image, it would be better to make an image from UIView (the topmost in the hierarchy, unless I understood wrong and you wan't the UIImageView part only)
let parentMostView = (the view you want to create into an image)
UIGraphicsBeginImageContextWithOptions(size: parentMostView.frame.size, NO, UIScreen.main.scale)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGrapgicsEndImageContext()
let imageAsData = image.pngData()
//do your base64 conversion here
EDITED: Upon seeing your image creation code
I don't see any issues in your getImage(:) method. On what device are you testing(lower scale devices would create low resolution images because you are dependent on Device scale)? Try using a high static scale value(e.g 10) and replace Data conversion to .jpegData(compressionQuality: 1.0) or .pngData()

How to reduce the image size coming from web services?

In the below shown image i am getting the images from web services and passing it to a table view but when scrolling up and down the image size was increasing and it is overlapping on labels and i had given constraints also can anyone help me how to avoid this ?
Hey you don't need to resize image.
First Set fix height width of your image view with constraints in tableview cell
Second Set imageview to aspectFit.
imageView.contentMode = UIViewContentModeScaleAspectFit;
Constraints add like this of your image view
Hope you will get success using that, if you any query regarding this , just comment will help
Using content mode to fit the image is an option but if you want to crop or resize it or compress image check the below code.
Call like let imageData = image.compressImage(rate: 0.5) and then you can provide data to write the image if needed.
func compressImage(rate: CGFloat) -> Data? {
return UIImageJPEGRepresentation(self, rate)
}
OR If you want to crop the image then,
func croppedImage(_ bound: CGRect) -> UIImage? {
guard self.size.width > bound.origin.x else {
print("X coordinate is larger than the image width")
return nil
}
guard self.size.height > bound.origin.y else {
print("Y coordinate is larger than the image height")
return nil
}
let scaledBounds: CGRect = CGRect(x: bound.x * self.scale, y: bound.y * self.scale, width: bound.w * self.scale, height: bound.h * self.scale)
let imageRef = self.cgImage?.cropping(to: scaledBounds)
let croppedImage: UIImage = UIImage(cgImage: imageRef!, scale: self.scale, orientation: UIImageOrientation.up)
return croppedImage
}
Make sure to add the above methods to UIImage Extension.
I had placed a view on table view cell and then I had placed all the elements and given constraints for view and elements in it then my problem has been reduced and working perfectly when scrolling also and is as shown in image below.
here is the layout for this screen

How to tint an animated UIImageView?

I'm attempting to apply tint of a UIImageView consisting of programatically loaded animationImages. Currently using iOS 9.3.
I think I've tried every proposed solution found here for applying the tint including:
Setting the .renderMode on load with: let newImage: UIImage? =
UIImage(named: filename as
String)!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
Setting the tintView either in the Storyboard or programatically
with: zeroImageView.tintColor = UIColor.redColor()
Setting the image .renderMode through the UIImageView itself:
zeroImageView.image = zeroImageView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
Setting the UIImageView's renderMode through the Interface Builder
user defined runtime attributes: imageRenderingMode Number 2
The image sequence is of PNGs with transparency, so, I would like to re-colour the image and maintain the transparency.
I've had no luck and am sort of at a loss. Wondering if maybe these methods only work for a still UIAnimationView? Any help would be greatly appreciated, thanks!
Here's some code:
// Load all images
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("png") {
let filename: NSString = "Digits/0/" + element
print(filename)
imageNames.append(filename as String)
let newImage: UIImage? = UIImage(named: filename as String)!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
images.append(newImage!)
zeroImageView.tintColor = UIColor.redColor()
}
}
// Make animated UIImageView
zeroImageView.userInteractionEnabled = false
zeroImageView.animationImages = images
zeroImageView.animationDuration = 2.0
zeroImageView.startAnimating()
zeroImageView.tintColor = UIColor.redColor()
Try this...
extension UIImage {
func image(withTint tint: UIColor) -> UIImage? {
guard let cgImage = cgImage else {
return nil
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
let rect = CGRect(origin: .zero, size: size)
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setBlendMode(.normal)
context.clip(to: rect, mask: cgImage)
tint.setFill()
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
If none of those worked the next step I would do is programmatically place an empty UIView on top of the UIImageView. First off, it would not interfere with anything going on in the UIImageView.
I would check out this tutorial here: http://www.apptuitions.com/programmatically-creating-uiview-uislider-uiswitch-using-swift/
this will teach you how to programmatically add a UIView to the storyboard. I would add this code in the viewDidLoad() method.
The main thing you need to change is the background color of the UIView. First you have to decide what the color of the tint (which from what I see in your code is red) and then explicitly change the 'Alpha' of the color so that it is barely showing. You change the 'Alpha' because that is basically the opacity of the color.
Hope this helps.

Pasteboard UIImage not using scale

I am building a custom keyboard and am having trouble adding an image to the pasteboard and maintaining the appropriate scale and resolution with in the pasted image. Let me start with a screenshot of the keyboard to illustrate my trouble:
So the picture of the face in the top left of the keyboard is just a UIButton with the original photo set to the background. When the button is pressed the image is resized with the following function:
func imageResize(image:UIImage, size:CGSize)-> UIImage {
let scale = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(size, false, scale)
var context = UIGraphicsGetCurrentContext()
CGContextSetInterpolationQuality(context, kCGInterpolationHigh)
image.drawInRect(CGRect(origin: CGPointZero, size: size))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage
}
This function creates a UIImage the same size as the UIButton with the appropriate scale to reflect the device's screen resolution. To verify that the function is correct, I added an UIImageView filled with the scaled image. The scaled image is the image that looks misplaced near the center of the keyboard. I added the UIImageView with this function:
func addImageToBottomRight(image: UIImage) {
var tempImgView = UIImageView(image: image)
self.view.addSubview(tempImgView)
tempImgView.frame.offset(dx: 100.0, dy: 50.0)
}
I have tried a few different methods for adding the image to the pasteboard, but all seem to ignore the scale of the image and display it twice as large as opposed to displaying it at a higher resolution:
let pb = UIPasteboard.generalPasteboard()!
var pngType = UIPasteboardTypeListImage[0] as! String
var jpegType = UIPasteboardTypeListImage[2] as! String
pb.image = image
pb.setData(UIImagePNGRepresentation(image), forPasteboardType: pngType)
pb.setData(UIImageJPEGRepresentation(image, 1.0), forPasteboardType: jpegType)
All three of these methods do not work correctly and produce the same result as illustrated in the screenshot. Does anyone have any suggestions of other methods? To further clarify my goal, I would like the image in the message text box to look identical to both UIImages in the keyboard in terms of size and resolution.
Here are a few properties of the UIImage before and resize in case anyone is curious:
Starting Image Size is (750.0, 750.0)
Size to scale to is: (78.0, 78.0))
Initial Scale: 1.0
Resized Image Size is (78.0, 78.0)
Resized Image Scale: 2.0
I know this is an old post, but thought I share the work around I found for this specific case of copying images and pasting to messaging apps.The thing is, when you send a picture with such apps like iMessages, whatsapp, messenger, etc, the way they display the image is so that it aspect fits to some certain horizontal width (lets say around 260 pts for this demo).
As you can see from the diagram below, if you send 150x150 image #1x resolution in imessage, it will be stretched and displayed in the required 260 width box, making the image grainy.
But if you add an empty margin of width 185 to both the left and right sides of the image, you will end up with an image of size 520x150. Now if you send that sized image in imessage, it will have to fit it in a 260 width box, ending up cramming a 520x150 image in a 260x75 box, in a way giving you a 75x75 image at #2x resolution.
You can add a clear color margin to a UIImage with a code like this
extension UIImage {
func addMargin(withInsets inset: UIEdgeInsets) -> UIImage? {
let finalSize = CGSize(width: size.width + inset.left + inset.right, height: size.height + inset.top + inset.bottom)
let finalRect = CGRect(origin: CGPoint(x: 0, y: 0), size: finalSize)
UIGraphicsBeginImageContextWithOptions(finalSize, false, scale)
UIColor.clear.setFill()
UIGraphicsGetCurrentContext()?.fill(finalRect)
let pictureOrigin = CGPoint(x: inset.left, y: inset.top)
let pictureRect = CGRect(origin: pictureOrigin, size: size)
draw(in: pictureRect)
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
defer { UIGraphicsEndImageContext() }
return finalImage
}
}

Resources