有没有办法让自定义类在 UIView 上起作用,而无需在初始化时传递 ViewController(作为参考)?
Posted
技术标签:
【中文标题】有没有办法让自定义类在 UIView 上起作用,而无需在初始化时传递 ViewController(作为参考)?【英文标题】:Is there a way to have a custom class act on a UIView without having the ViewController passed (as reference) upon its initialization? 【发布时间】:2017-12-15 13:32:52 【问题描述】:示例:我有一个 SpeechSynthesizer 类,它需要在我的 UIView 完成一段文本的发音时更新它。由于 SpeechSynthesizer 类符合协议 AVSpeechSynthesizerDelegate,因此它是在发声完成时接收 didFinish 信号的类。这里的想法是防止 ViewController 有太多的委托方法和一长串需要遵守的协议。我发现的解决方法是将 ViewController 作为 SpeechSynthesizer 初始化参数传入。通过这种方式,我可以从 SpeechSynthesizer 类中访问连接到要更新的 UIView 的 ViewController。我不喜欢它的一点是,将 ViewController 作为参数传递给每个需要使用它的类看起来有点难看。所以我想知道,我还有什么方法可以做到这一点。
我想问这个问题的另一种方式是:我怎样才能制作函数
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
将某些内容返回给 ViewController,因为它没有被它“调用”?
【问题讨论】:
您应该使用委托模式。这是解耦对象的适当方式。 或者,您可以传递一个执行 ViewController 操作的闭包,而不是将 ViewController 作为 SpeechSynthesizer 初始化参数传入。 【参考方案1】:我在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 上添加了一个相机预览层。为此,它需要访问主视图的层。
上面的代码仍然不能工作,因为我们原来的 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
希望这对其他人有用。
干杯!
【讨论】:
以上是关于有没有办法让自定义类在 UIView 上起作用,而无需在初始化时传递 ViewController(作为参考)?的主要内容,如果未能解决你的问题,请参考以下文章
如何让自定义 UIControl 作为推送操作参与 segue?