这是 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 中使用它,它会完美地工作。draw
scaleAspectFit
接下来,一个“包装器”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 视图)将发送通知以更新图像。