iOS TextView 在缩放时保存模糊

iOS TextView is saved blurry when scaled

我尝试将 textview 保存为图像而不是设备比例。我实现了一种方法,通过根据 UI 值添加任意 textview 来保存图像。因为当我尝试在放大时使用 drawHierarchy 方法保存图像时,图像很模糊。

textview保存模糊的情况

这是我的代码

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
    }
}

有线点

当textview的高度大于128时,textview保存模糊。当我把 textview 默认高度设置为 128 时,我发现了相关的值。

高度为128或更小(当isScrollEnabled为false时),textview保存总是清晰的。但是当高度超过128时,它看起来很模糊。

身高128

身高129

我想知道如何使用 textview 以 @5x 比例清楚地绘制图像。 (textview 高度大于 128)

这是一个简单的例子,使用来自这个已接受答案的 UIView 扩展:

我们将创建一个大小为 240 x 129UITextView。然后添加 4 个按钮以捕获 1x、2x、5x 和 10x 比例的文本视图。

当运行:

时看起来像这样

结果...

1x 比例下 - 240 x 129 像素:

2x 比例下 - 480 x 258 像素:

5x 比例下 - 1200 x 645 像素(仅显示一部分):

10x 比例下 - 2400 x 1290 像素(仅显示一部分):

分机:

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
    }
}

示例控制器代码:

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

}