首页 > 解决方案 > SwiftUI 中的 GLKView?

问题描述

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

标签: swiftuicore-imagecifilterglkview

解决方案


这是 SwiftUI 中使用UIViewControllerRepresentable.

有几件事要记住。

  • GLKit近 2 年前,随着 iOS 12 的发布而被弃用。虽然我希望 Apple 不会很快杀死它(太多的应用程序仍在使用它),但他们建议使用 Metal 或 anMTKView来代替。这里的大部分技术仍然适用于 SwiftUI。

  • 我与 SwiftUI 合作,希望让我的下一个 CoreImage 应用程序成为一个“纯粹的”SwiftUI 应用程序,直到我需要引入太多的 UIKit。我在 Beta 6 前后停止了这个工作。代码有效,但显然还没有准备好生产。这个的回购是here

  • 我更喜欢使用模型而不是CIFilter直接在我的视图中使用代码。我假设您知道如何创建视图模型并将其设置为EnvironmentObject. 如果不查看我在 repo 中的代码。

  • 您的代码引用了 SwiftUIImage视图 - 我从未找到任何文档表明它使用 GPU(就像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)
        }
    }

}

这是非常古老的生产代码,取自2015 年 2 月的 objc.io 第 21 期!值得注意的是,它封装了 a ,需要在使用它的方法之前CIContext定义它自己的清晰颜色,并将图像渲染为. 如果你应该尝试在 UIKit 中使用它,它会完美地工作。drawscaleAspectFit

接下来,一个“包装器”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。去年我花了一周的时间试图让组合“正常工作”——尤其是在让UIButton水龙头通知我的模型发生变化的相反方向上——并发现这确实是最简单的方法。它甚至比使用委托方法更容易。

接下来,将 VC 公开为可表示对象:

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在 VC 中设置变量的地方。我确信完全摆脱 VC 并拥有一个 是可能的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 视图)将发送通知以更新图像。


推荐阅读