如何制作一个自适应大小的 UiImageView?

How to make a Self-sizing UiImageView?

我需要一个可以重复使用的简单 QR 码 class。我已经创建了 class 并且它可以工作,但是不需要手动设置大小限制,因为它需要根据设备的 DPI 调整其大小。在这个最小的示例中,我只使用 100,因为大小计算代码不相关(在 IB 中设置为 50)。此外,我将在不同位置放置多个二维码,我将通过 IB 管理它们的定位。但至少我希望能够在代码中设置宽高限制。

下面的代码显示了一个 QR 码,大小正确(在运行时设置),但是当约束设置为水平和垂直居中时,它不会。同样,我不想要 IB 中的大小限制,但我确实想要 IB 中的位置限制

import Foundation
import UIKit

@IBDesignable class QrCodeView: UIImageView {
    var content:String = "test" {
        didSet {
            generateCode(content)
        }
    }
    lazy var filter = CIFilter(name: "CIQRCodeGenerator")
    lazy var imageView = UIImageView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = CGRect(x:0, y:0, width:100, height:100)
        frame = CGRect(x:frame.origin.x, y:frame.origin.y, width:100, height:100)

    }

    func setup() {
        //translatesAutoresizingMaskIntoConstraints = false
        generateCode(content)


        addSubview(imageView)
        layoutIfNeeded()

    }

    func generateCode(_ string: String) {
        guard let filter = filter,
        let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
            return
        }

        filter.setValue(data, forKey: "inputMessage")

        guard let ciImage = filter.outputImage else {
            return
        }
        let transform = CGAffineTransform(scaleX: 10, y: 10)
        let scaled = UIImage(ciImage: ciImage.transformed(by: transform))

        imageView.image = scaled
    }

}

我相信你让事情变得比需要的更复杂...

让我们从一个简单的 @IBDesignable UIImageView subclass.

开始

从一个新项目开始并添加此代码:

@IBDesignable
class MyImageView: UIImageView {

    // we'll use this later
    var myIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
    override var intrinsicContentSize: CGSize {
        return myIntrinsicSize
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setup()
        self.image = UIImage()
    }
    
    func setup() {
        backgroundColor = .green
        contentMode = .scaleToFill
    }

}

现在,在 Storyboard 中,将 UIImageView 添加到视图控制器。将其自定义 class 设置为 MyImageView 并设置水平和垂直中心约束。

图像视图应自动将自身大小调整为 100 x 100,以绿色背景的视图为中心(我们只是设置背景以便我们可以看到它):

运行 应用程序,您应该会看到相同的内容。

现在,将其作为 @IBOutlet 添加到视图控制器:

class ViewController: UIViewController {

    @IBOutlet var testImageView: MyImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        testImageView.myIntrinsicSize = CGSize(width: 300.0, height: 300.0)
    }
}

运行 应用程序,您会看到居中的绿色图像视图,但现在它将是 300 x 300 点而不是 100 x 100

您的其余任务几乎就是在渲染 QRCode 图像后添加代码来设置此自定义 class 的 .image 属性。

这是习俗 class:

@IBDesignable
class QRCodeView: UIImageView {
    
    // so we can test changing the QRCode content in IB
    @IBInspectable
    var content:String = "test" {
        didSet {
            generateCode(content)
        }
    }
    
    var qrIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
    override var intrinsicContentSize: CGSize {
        return qrIntrinsicSize
    }
    
    lazy var filter = CIFilter(name: "CIQRCodeGenerator")
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setup()
        generateCode(content)
    }
    
    func setup() {
        contentMode = .scaleToFill
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        generateCode(content)
    }
    
    func generateCode(_ string: String) {
        guard let filter = filter,
            let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
                return
        }
        
        filter.setValue(data, forKey: "inputMessage")
        
        guard let ciImage = filter.outputImage else {
            return
        }
        
        let scX = bounds.width / ciImage.extent.size.width
        let scY = bounds.height / ciImage.extent.size.height

        let transform = CGAffineTransform(scaleX: scX, y: scY)

        let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
        
        self.image = scaled
        
    }
    
}

在故事板/IB 中:

这是一个示例视图控制器:

class ViewController: UIViewController {
    
    @IBOutlet var qrCodeView: QRCodeView!
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        // calculate your needed size
        //  I'll assume it ended up being 240 x 240
        qrCodeView.qrIntrinsicSize = CGSize(width: 240.0, height: 240.0)
    }
    
}

编辑

这是修改后的 QRCodeView class,它将自身调整为(物理)15x15 毫米图像。

我使用 https://github.com/devicekit/DeviceKit 中的 DeviceKit 来获取当前设备的 ppi。请参阅评论以将其替换为您自己的(假设您已经在使用其他东西)。

实例化此 class 时,它将:

  • 获取当前设备的ppi
  • 将 ppi 转换为每毫米像素
  • 计算 15 x 每毫米像素
  • 根据屏幕比例转换
  • 更新其固有大小

QRCodeViewUIImageView 的子class)只需要位置限制...因此您可以使用顶部 + 前导、顶部 + 尾随、中心 X 和 Y、底部+ CenterX 等等等等

@IBDesignable
class QRCodeView: UIImageView {
    
    @IBInspectable
    var content:String = "test" {
        didSet {
            generateCode(content)
        }
    }
    
    var qrIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
    override var intrinsicContentSize: CGSize {
        return qrIntrinsicSize
    }
    
    lazy var filter = CIFilter(name: "CIQRCodeGenerator")
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setup()
        generateCode(content)
    }
    
    func setup() {
        contentMode = .scaleToFill
        
        // using DeviceKit from https://github.com/devicekit/DeviceKit
        // replace with your lookup code that gets
        //  the device's ppi
        let device = Device.current
        guard let ppi = device.ppi else { return }
        
        // convert to pixels-per-millimeter
        let ppmm = CGFloat(ppi) / 25.4
        // we want 15mm size
        let mm15 = 15.0 * ppmm
        // convert based on screen scale
        let mmScale = mm15 / UIScreen.main.scale
        // update our intrinsic size
        self.qrIntrinsicSize = CGSize(width: mmScale, height: mmScale)

    }
    override func layoutSubviews() {
        super.layoutSubviews()
        generateCode(content)
    }
    
    func generateCode(_ string: String) {
        guard let filter = filter,
            let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
                return
        }
        
        filter.setValue(data, forKey: "inputMessage")
        
        guard let ciImage = filter.outputImage else {
            return
        }
        
        let scX = bounds.width / ciImage.extent.size.width
        let scY = bounds.height / ciImage.extent.size.height

        let transform = CGAffineTransform(scaleX: scX, y: scY)

        let scaled = UIImage(ciImage: ciImage.transformed(by: transform))

        self.image = scaled
        
    }
    
}