这是一个使用
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)
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视图)将发送通知以更新图像。
UIViewRepresentable
完成了这个任务。基本上,我的UIKit
应用程序都使用一个子类化的GLKView
(出于一些原因,比如在需要时创建缩放的UIImage
等),而且将其转换为UIViewRepresentable
实际上也相当简单。如果你想要的话,我可以找出代码 - 我决定在Beta 5版本之前,SwiftUI对于完整的生产应用程序来说还太新了 - 并在这个周末之后尝试发布它。 - user7014451MTKView
。(我的生产应用程序目前还没有使用。) - user7014451