有没有办法让自定义类在UIView上进行操作而不需要在初始化时传递ViewController(作为参考)?

时间:2017-12-15 13:32:52

标签: ios xcode uiview uiviewcontroller swift4

示例:我有一个SpeechSynthesizer类,当它完成一段文本时需要更新我的UIView中的内容。由于SpeechSynthesizer类符合协议AVSpeechSynthesizerDelegate,因此它在发声完成时接收didFinish信号。这里的想法是让ViewController不要有太多的委托方法和一长串协议来遵守。我发现的解决方法是将ViewController作为SpeechSynthesizer初始化参数传入。这样我就可以访问连接到我想要从SpeechSynthesizer类内部更新的UIView的ViewController。我不喜欢它的事情是,将ViewController作为参数传递给需要使用它的每个类,看起来很丑陋。所以我想知道,除此之外,我还能做到这一点。

我想另一种问这个问题的方法是:如何制作这个功能

private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)

将某些内容返回给ViewController,因为它没有"调用"它呢?

1 个答案:

答案 0 :(得分:0)

我在Quora上添加了回复。在这里复制:

在对我自己的代码进行一些研究和测试后,这是解决这个问题的两种方法。

解决方案1:代表模式:

在ViewController中创建自定义委托协议

protocol ViewControllerDelegate:class {
    func getViewLayer() -> CALayer
}

ViewController必须符合这个新创建的协议,因此实现了它定义的所有函数,所以你在ViewController类中添加:

public func getViewLayer() -> CALayer {
    return self.view.layer
}

然后在我的自定义类ReadTextMachine上,我添加了一个ViewControllerDelegate类型的变量

private weak var viewControllerDelegate: ViewControllerDelegate?

变量必须是弱的,协议必须是类型类才能解决“保留周期”问题(因为自定义类和ViewController都会指向彼此)

现在你会注意到ViewController中的函数调用已经从自定义类中“调用”了,所以在我的ReadTextMachine中我添加了:

let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()

在上面的例子中,我的CameraPreview(是的,本例中的第3类)只是在UIView上添加了一个摄像头预览图层。为此,它需要访问主View的图层。

上面的代码仍然无法正常工作,因为我们的原始viewController实例尚未在代码中的任何位置作为引用传递。为此,我们在ReadTextMachine中添加以下函数:

public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
    self.viewControllerDelegate = viewController
}

在我们实例化自定义类(ReadTextMachine)之后,必须从ViewController调用上面的代码,以便它内部的viewControllerDelegate指向ViewController。所以在我们的ViewController.swift中:

operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)

另一个示例和解释可以在LetsBuildThatApp的video中找到。我主要从它那里得到了我的解决方案。

我现在使用上述解决方案开发的应用程序可以在这里找到:agu3rra/World-Aloud

解决方案2:通知和观察员模式

这个更易于理解和遵循。一般的想法是让您的自定义类广播一条消息,该消息触发ViewController上的函数调用,因为它具有观察者设置,等待听到该消息。

举一个例子,在我使用它的上下文中,我有一个CameraCapture类,它使用AVFoundation来捕获照片。捕获照片触发器无法立即返回图像,因为iOS在实际生成图像之前有一组要执行的步骤。我希望我的ReadTextMachine在CameraCapture有照片后恢复活动。 (要在CustomClass触发器的上下文中应用它,ViewController事件基本相同,因为两者都是iOS应用程序中的实际类)。

所以我做的第一件事是创建一个广播功能,因为我会在我的应用程序的许多地方使用它。我只是将它放在Xcode项目的Utilities.swift文件中。

public func broadcastNotification(name: String) {
    let notification = Notification.Name(rawValue: name)
    NotificationCenter.default.post(name: notification, object: nil)
}

上述函数接受一个字符串,该字符串必须是唯一的通知标识符,并通过NotificationCenter广播它。

在我的CameraCapture类中,我添加了一个静态常量来引用消息的唯一标识符:

static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"

对于那些了解AVFoundation的人,当事件didFinishProcessingPhoto被执行时,照片可用,所以在最后我添加了:

broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)

以上是对我之前定义的效用函数的调用。

为了使我的ReadTextMachine类能够捕获该通知,我在其init()和deinit例程中添加了以下内容:

 override init() {
            super.init()

            // Setup event observers
            let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(self.processingDoneTakingPhoto),
                                                   name: notification1,
                                                   object: nil)
    }

deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
            }

删除观察者在deinit中非常重要,这样当你的对象从内存中释放出来时,观察者就不会留下来徘徊。上面配置的观察者在ReadTextMachine中触发一个函数调用:

@IBAction private func processingDoneTakingPhoto() {
    // does my stuff
}

就是这样!同样,整个Xcode项目可以从我项目的Git存储库下载:agu3rra/World-Aloud

希望这对其他人有用。

干杯!