I tried save textview as image with not device scale. I implemented a method to save an image by adding an arbitrary textview according to the UI value. Because when I tried save image using drawHierarchy method in up scale, image was blurry.
Condition when textview is saved blurry
here is my code
func drawQuoteImage() {
var campusSize = view.frame.size
var scale = UIScreen.main.scale + 2
// 1. Create View
let quoteView = UIView(frame: CGRect(x: 0, y: 0, width: campusSize.width, height: campusSize.height))
let textview = UITextView()
textview.attributedText = NSAttributedString(string: quoteLabel.text, attributes: textAttributes as [NSAttributedString.Key : Any])
textview.frame = transfromFrame(originalFrame: quoteLabel.frame, campusSize: campusSize)
quoteView.addSubview(textview)
// 2. Render image
UIGraphicsBeginImageContextWithOptions(quoteView.frame.size, false, scale)
let context = UIGraphicsGetCurrentContext()!
context.setRenderingIntent(.relativeColorimetric)
context.interpolationQuality = .high
quoteView.drawHierarchy(in: quoteView.frame, afterScreenUpdates: true)
quoteView.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
quoteImage = image
}
private func transfromFrame(originalFrame: CGRect, campusSize: CGSize) -> CGRect
{
if UIDevice.current.screenType == .iPhones_X_XS {
return CGRect(x: round(originalFrame.origin.x), y: round(originalFrame.origin.y), width: round(originalFrame.width), height: round(originalFrame.height))
}
else {
var frame = CGRect()
let ratioBasedOnWidth = campusSize.width / editView.frame.width
let ratioBasedOnHeight = campusSize.height / editView.frame.height
frame.size.width = round(originalFrame.width * ratioBasedOnWidth)
frame.size.height = round(originalFrame.height * ratioBasedOnHeight)
frame.origin.x = round(originalFrame.origin.x * ratioBasedOnWidth)
frame.origin.y = round(originalFrame.origin.y * ratioBasedOnHeight)
return frame
}
}
Wired Point
when height of textview is more than 128, textview is save blurry. I found related value when I put textview default height is 128.
Height is 128 or less (when isScrollEnabled is false), textview is saved always clear. But when height is more than 128, it looks blurry.
Height 128
Height 129
I'd like to know how to clearly draw image with textview at @5x scale. (textview height is bigger than 128)
Here's a quick example using a UIView
extension from this accepted answer: https://stackoverflow.com/a/51944513/6257435
We'll create a UITextView
with a size of 240 x 129
. Then add 4 buttons to capture the text view at 1x, 2x, 5x and 10x scale.
It looks like this when running:
and the result...
At 1x
scale - 240 x 129 pixels:
At 2x
scale - 480 x 258 pixels:
At 5x
scale - 1200 x 645 pixels (just showing a portion):
At 10x
scale - 2400 x 1290 pixels (just showing a portion):
The extension:
extension UIView {
func scale(by scale: CGFloat) {
self.contentScaleFactor = scale
for subview in self.subviews {
subview.scale(by: scale)
}
}
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
}
}
Sample controller code:
class TextViewCapVC: UIViewController {
let textView = UITextView()
let resultLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// add a stack view with buttons
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 12
[1, 2, 5, 10].forEach { i in
let btn = UIButton()
btn.setTitle("Create Image at \(i)x scale", for: [])
btn.setTitleColor(.white, for: .normal)
btn.setTitleColor(.lightGray, for: .highlighted)
btn.backgroundColor = .systemBlue
btn.tag = i
btn.addTarget(self, action: #selector(gotTap(_:)), for: .touchUpInside)
stack.addArrangedSubview(btn)
}
[textView, stack, resultLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// text view 280x240, 20-points from top, centered horizontally
textView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
textView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
textView.widthAnchor.constraint(equalToConstant: 240.0),
textView.heightAnchor.constraint(equalToConstant: 129.0),
// stack view, 20-points from text view, same width, centered horizontally
stack.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 20.0),
stack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack.widthAnchor.constraint(equalTo: textView.widthAnchor),
// result label, 20-points from stack view
// 20-points from leading/trailing
resultLabel.topAnchor.constraint(equalTo: stack.bottomAnchor, constant: 20.0),
resultLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
resultLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
let string = "Test"
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.blue,
.font: UIFont.italicSystemFont(ofSize: 104.0),
]
let attributedString = NSMutableAttributedString(string: string, attributes: attributes)
textView.attributedText = attributedString
resultLabel.font = .systemFont(ofSize: 14, weight: .light)
resultLabel.numberOfLines = 0
resultLabel.text = "Results:"
// so we can see the view frames
textView.backgroundColor = .yellow
resultLabel.backgroundColor = .cyan
}
@objc func gotTap(_ sender: Any?) {
guard let btn = sender as? UIButton else { return }
let scaleFactor = CGFloat(btn.tag)
let img = textView.getImage(scale: scaleFactor)
var s: String = "Results:\n\n"
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fName: String = "\(btn.tag)xScale-\(img.size.width * img.scale)x\(img.size.height * img.scale).png"
let url = documents.appendingPathComponent(fName)
if let data = img.pngData() {
do {
try data.write(to: url)
} catch {
s += "Unable to Write Image Data to Disk"
resultLabel.text = s
return
}
} else {
s += "Could not get png data"
resultLabel.text = s
return
}
s += "Logical Size: \(img.size)\n\n"
s += "Scale: \(img.scale)\n\n"
s += "Pixel Size: \(CGSize(width: img.size.width * img.scale, height: img.size.height * img.scale))\n\n"
s += "File \"\(fName)\"\n\nsaved to Documents folder\n"
resultLabel.text = s
// print the path to documents in debug console
// so we can copy/paste into Finder to get to the files
print(documents.path)
}
}