有没有办法让自定义类在 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(作为参考)?的主要内容,如果未能解决你的问题,请参考以下文章

知道为啥下面的 UIView 动画只在一个方向上起作用吗?

有没有办法让自定义目标依赖于 CPack? [复制]

如何让自定义 UIControl 作为推送操作参与 segue?

UITableView 自定义单元格在 iPad Storyboard 上不起作用,但在 iPhone 上起作用

对于drawRect使用,谨慎使用!

如何淡化 UIView 背景颜色 - iOS 5