SwiftUI中的GLKView?

3
我该如何在SwiftUI中使用GLKView?我正在使用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)
        }
    }
}

我通过使用UIViewRepresentable完成了这个任务。基本上,我的UIKit应用程序都使用一个子类化的GLKView(出于一些原因,比如在需要时创建缩放的UIImage等),而且将其转换为UIViewRepresentable实际上也相当简单。如果你想要的话,我可以找出代码 - 我决定在Beta 5版本之前,SwiftUI对于完整的生产应用程序来说还太新了 - 并在这个周末之后尝试发布它。 - user7014451
还有一个额外的评论 - 请记住,自iOS 12起,所有与OpenGL/GLKit相关的内容都已被弃用。我相当希望它们在iOS 14上仍然能正常工作,但是...苹果希望你使用Metal,具体来说是MetalKit和MTKView。(我的生产应用程序目前还没有使用。) - user7014451
2个回答

3
这是一个使用UIViewControllerRepresentable在SwiftUI中工作的GLKView。
需要记住以下几点。
  • 在近两年前的iOS 12发布时,GLKit已被弃用。虽然我希望苹果不会很快杀死它(因为还有太多应用程序在使用它),但他们建议改用Metal或者MTKView。大部分技术仍适用于SwiftUI。

  • 我曾尝试使用SwiftUI来制作我的下一个CoreImage应用程序,直到我需要引入过多的UIKit功能才停止了Beta 6的工作。代码可以运行,但显然不够成熟。这个库的链接在这里

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

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

}

这是非常老的生产代码,来自于objc.io 2015年2月刊第21期!值得注意的是,它封装了一个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 点击通知我的模型进行更改 - 并发现这确实是最简单的方法。它甚至比使用委托方法更容易。
接下来,将 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
    }
}

唯一值得注意的是这里是我在 VC 中设置 `model` 变量的地方。我确定完全可以摆脱 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视图)将发送通知以更新图像。

谢谢 @dfd,我现在正在检查代码 :) 在使用SwiftUI之前,我有一个类似于你的实现(子类化GLKView),但是在SwiftUI的新范式下,现在如何做事情有点困惑 - 比如使用GLKit还是Metal :/ 这太复杂了 - Aнгел
我学到了两个关键的东西。(1) 使用可表示性,一切都是视图 - 直到不是为止。可绑定性、UIKit 视图和视图控制器生命周期等之间的连接,迫使我在深入研究时退缩了。 (2) 我最终需要至少三个可表示性 - UIImagePickerController、UIActivityViewController 和 GLKView。这些在 SwiftUI 中都不存在。虽然我同意你关于范式转变的观点,但这些确实是未完成的部分,苹果可能需要数年时间才能将它们移植到 SwiftUI。 - user7014451

0

苹果公司的WWDC 2022包含了一个名为“使用Core Image、Metal和SwiftUI显示EDR内容”的教程/视频,其中描述了如何将Core Image与Metal和SwiftUI混合。它指向一些新的示例代码,名为“使用Core Image Render Destination生成动画”(此处)。

虽然它没有回答关于使用GLKView的问题,但它提供了一些优雅、干净、经过苹果认可的在SwiftUI中使用Metal的代码。

这个示例项目非常以CoreImage为中心(与您的CIFilter背景相匹配),但我希望苹果能够发布更多的示例代码,展示Metal与SwiftUI的集成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接