Как сделать саморазмерный UiImageView? - PullRequest
0 голосов
/ 16 июня 2020

Мне нужен простой класс QR-кода, который я могу использовать повторно. Я создал класс, и он работает, однако ручная установка ограничений размера нежелательна, потому что ему необходимо настроить его размер в зависимости от DPI устройства. Здесь, в этом минимальном примере, я просто использую 100, поскольку код расчета размера не имеет значения (установлен на 50 в IB). Также у меня будет несколько QR-кодов на разных позициях, и я буду управлять их позиционированием с помощью 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
    }

}

1 Ответ

0 голосов
/ 19 июня 2020

Я считаю, что вы делаете это более сложным, чем нужно ...

Давайте начнем с простого подкласса @IBDesignable UIImageView.

Начните с нового проекта и добавьте этот код:

@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 к контроллеру представления. Установите его собственный класс на MyImageView и установите ограничения по горизонтали и вертикали по центру.

Вид изображения должен автоматически иметь размер 100 x 100, центрированный в виде с зеленым фоном (мы просто устанавливаем фон чтобы мы могли его увидеть):

enter image description here

Run the app, and you should see the same thing.

Now, add it as an @IBOutlet to a view controller:

class ViewController: UIViewController {

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

Run the app, and you will see a centered green image view, but now it will be 300 x 300 points instead of 100 x 100.

The rest of your task is pretty much adding code to set this custom class's .image property once you've rendered the QRCode image.

Here's the custom 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
        
    }
    
}

In Storyboard / IB:

enter image description here

And here's an example view controller:

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

Edit

Here's a modified QRCodeView class that will size itself to a (physical) 15x15 mm image.

I used DeviceKit from https://github.com/devicekit/DeviceKit, чтобы получить ppi текущего устройства. См. Комментарий, чтобы заменить его своим собственным (при условии, что вы уже используете что-то другое).

При создании экземпляра этого класса он:

  • получит ppi текущего устройства
  • преобразование ppi в пиксели на миллиметр
  • вычисление 15 x пикселей на миллиметр
  • преобразование на основе масштаба экрана
  • обновление его внутреннего c размера

Для QRCodeView (подкласс UIImageView) нужны только ограничения позиции ... поэтому вы можете использовать Top + Leading, Top + Trailing, Center X & Y, Bottom + CenterX и т. Д. c, и т.д. c.

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