Вот рабочий 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) отправит уведомление для обновления изображение.