GLKView в SwiftUI? - PullRequest
       79

GLKView в SwiftUI?

0 голосов
/ 14 января 2020

Как я могу использовать GLKView в SwiftUI? Я использую CIFilter, но хотел бы применить фильтры через GLKit / OpenGL. Есть идеи?

struct ContentView: View {
    @State private var image: Image?

    var body: some View {
        VStack {
            image?
                .resizable()
                .scaledToFit()
        }
        .onAppear(perform: loadImage)
    }

    func loadImage() {
        guard let inputImage = UIImage(named: "squirrel") else {
            return
        }
        let ciImage = CIImage(image: inputImage)

        let context = CIContext()           
        let blur = CIFilter.gaussianBlur()
        blur.inputImage = ciImage
        blur.radius = 20

        guard let outputImage = blur.outputImage else {
            return
        }
        if let cgImg = context.createCGImage(outputImage, from: ciImage!.extent) {
            let uiImg = UIImage(cgImage: cgImg)
            image = Image(uiImage: uiImg)
        }
    }
}

1 Ответ

0 голосов
/ 19 января 2020

Вот рабочий GLKView в SwiftUI, использующий UIViewControllerRepresentable.

Несколько вещей, о которых следует помнить.

  • GLKit устарел с выпуском iOS 12, почти 2 года go. Хотя я надеюсь, что Apple не убьет его в ближайшее время (слишком много приложений все еще используют его), они рекомендуют использовать Metal или MTKView вместо этого. Большая часть этой техники до сих пор - путь к go для SwiftUI.

  • Я работал с SwiftUI в надежде превратить мое следующее приложение CoreImage в «чистое» приложение SwiftUI, пока не получу слишком много UIKit нужно внести. Я перестал работать над этим в бета-версии 6. Код работает, но явно не готов к работе. Репозиторий для этого здесь .

  • Мне удобнее работать с моделями, вместо того, чтобы помещать код для таких вещей, как CIFilter, прямо в мои представления. Я предполагаю, что вы знаете, как создать модель представления и сделать ее EnvironmentObject. Если нет, посмотрите на мой код в репозитории.

  • Ваш код ссылается на представление SwiftUI Image - я не нашел ни одной документации, в которой предлагается использовать графический процессор (как это делает GLKView) ) так что вы не найдете ничего подобного в моем коде. Если вы ищете производительность в реальном времени при изменении атрибутов, я обнаружил, что это работает очень хорошо.

Начиная с GLKView, вот мой код:

class ImageView: GLKView {

    var renderContext: CIContext
    var myClearColor:UIColor!
    var rgb:(Int?,Int?,Int?)!

    public var image: CIImage! {
        didSet {
            setNeedsDisplay()
        }
    }
    public var clearColor: UIColor! {
        didSet {
            myClearColor = clearColor
        }
    }

    public init() {
        let eaglContext = EAGLContext(api: .openGLES2)
        renderContext = CIContext(eaglContext: eaglContext!)
        super.init(frame: CGRect.zero)
        context = eaglContext!
    }

    override public init(frame: CGRect, context: EAGLContext) {
        renderContext = CIContext(eaglContext: context)
        super.init(frame: frame, context: context)
        enableSetNeedsDisplay = true
    }

    public required init?(coder aDecoder: NSCoder) {
        let eaglContext = EAGLContext(api: .openGLES2)
        renderContext = CIContext(eaglContext: eaglContext!)
        super.init(coder: aDecoder)
        context = eaglContext!
    }

    override public func draw(_ rect: CGRect) {
        if let image = image {
            let imageSize = image.extent.size
            var drawFrame = CGRect(x: 0, y: 0, width: CGFloat(drawableWidth), height: CGFloat(drawableHeight))
            let imageAR = imageSize.width / imageSize.height
            let viewAR = drawFrame.width / drawFrame.height
            if imageAR > viewAR {
                drawFrame.origin.y += (drawFrame.height - drawFrame.width / imageAR) / 2.0
                drawFrame.size.height = drawFrame.width / imageAR
            } else {
                drawFrame.origin.x += (drawFrame.width - drawFrame.height * imageAR) / 2.0
                drawFrame.size.width = drawFrame.height * imageAR
            }
            rgb = (0,0,0)
            rgb = myClearColor.rgb()
            glClearColor(Float(rgb.0!)/256.0, Float(rgb.1!)/256.0, Float(rgb.2!)/256.0, 0.0);
            glClear(0x00004000)
            // set the blend mode to "source over" so that CI will use that
            glEnable(0x0BE2);
            glBlendFunc(1, 0x0303);
            renderContext.draw(image, in: drawFrame, from: image.extent)
        }
    }

}

Это очень старый производственный код, взятый из obj c .io выпуск 21 от февраля 2015 года ! Следует отметить, что он инкапсулирует CIContext, нуждается в собственном чистом цвете, определенном до с использованием метода draw, и отображает изображение как scaleAspectFit. Если вы попробуете использовать это в UIKit, он будет отлично работать.

Далее, "упаковщик" UIViewController:

class ImageViewVC: UIViewController {
    var model: Model!
    var imageView = ImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view = imageView
        NotificationCenter.default.addObserver(self, selector: #selector(updateImage), name: .updateImage, object: nil)
    }
    override func viewDidLayoutSubviews() {
        imageView.setNeedsDisplay()
    }
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        if traitCollection.userInterfaceStyle == .light {
            imageView.clearColor = UIColor.white
        } else {
            imageView.clearColor = UIColor.black
        }
    }
    @objc func updateImage() {
        imageView.image = model.ciFinal
        imageView.setNeedsDisplay()
    }
}

Я сделал это по нескольким причинам - добавив довольно много до того, что я не эксперт по Combine.

Во-первых, обратите внимание, что модель вида (model) не может получить прямой доступ к EnvironmentObject. Это объект SwiftUI, и UIKit об этом не знает. Я думаю, что ObservableObject * может работать, но никогда не находил правильный способ сделать это.

Во-вторых, обратите внимание на использование NotificationCenter. В прошлом году я потратил неделю на то, чтобы заставить Combine «просто работать» - особенно в направлении, противоположном нажатию UIButton, чтобы уведомить мою модель об изменении - и обнаружил, что это действительно самый простой способ. Это даже проще, чем использование методов делегата.

Далее, представив V C как представление:

struct GLKViewerVC: UIViewControllerRepresentable {
    @EnvironmentObject var model: Model
    let glkViewVC = ImageViewVC()

    func makeUIViewController(context: Context) -> ImageViewVC {
        return glkViewVC
    }
    func updateUIViewController(_ uiViewController: ImageViewVC, context: Context) {
        glkViewVC.model = model
    }
}

Единственное, что следует отметить, это то, где я установил model переменная в V C. Я уверен, что можно полностью избавиться от V C и получить UIViewRepresentable, но мне удобнее с этой настройкой.

Далее моя модель:

class Model : ObservableObject {
    var objectWillChange = PassthroughSubject<Void, Never>()

    var uiOriginal:UIImage?
    var ciInput:CIImage?
    var ciFinal:CIImage?

    init() {
        uiOriginal = UIImage(named: "vermont.jpg")
        uiOriginal = uiOriginal!.resizeToBoundingSquare(640)
        ciInput = CIImage(image: uiOriginal!)?.rotateImage()
        let filter = CIFilter(name: "CIPhotoEffectNoir")
        filter?.setValue(ciInput, forKey: "inputImage")
        ciFinal = filter?.outputImage
    }
}

Здесь вообще ничего не видно, но поймите, что в SceneDelegate, где вы его создаете, он вызовет init и настроит отфильтрованное изображение.

Наконец, ContentView:

struct ContentView: View {
    @EnvironmentObject var model: Model
    var body: some View {
        VStack {
            GLKViewerVC()
            Button(action: {
                self.showImage()
            }) {
                VStack {
                    Image(systemName:"tv").font(Font.body.weight(.bold))
                    Text("Show image").font(Font.body.weight(.bold))
                }
                .frame(width: 80, height: 80)
            }
        }
    }
    func showImage() {
        NotificationCenter.default.post(name: .updateImage, object: nil, userInfo: nil)
    }
}

SceneDelegate создает модель представления, которая теперь имеет измененный CIImage, а кнопка под GLKView (экземпляр GLKViewVC, который является просто представлением SwiftUI) отправит уведомление для обновления изображение.

...