私有的IBOutlets Swift

7

我知道我们的IBOutlets应该是私有的,但是如果我在TableViewCell中有IBOutlets,那么我应该如何从另一个ViewController中访问它们呢?这就是我问这种问题的原因:

class BookTableViewCell: UITableViewCell {

    @IBOutlet weak private var bookTitle: UILabel!

}

如果我将IBOutlet指定为私有(private),在另一个视图控制器中访问cell属性时会出现错误:'bookTitle'由于受到'private'保护级别的限制而无法访问


3
永远不应该从另一个视图控制器中访问插座。如果您觉得需要这样做,那么就存在问题,您的架构可能有其他严重问题。 - matt
1
为什么你想要将某些东西标记为私有,但又想从其他视图控制器中访问它呢?这对我来说听起来真的很有趣。为什么不创建一个可从另一个 VC 访问的属性,在其中调用 didSet {},然后在 set 中将该属性设置为视图控制器内部的标签? - Dominik Bucher
@matt 你的意思是什么?如果我有TableViewController和TableViewCell,你想让我在TableViewController中包含所有与TableViewCell相关的信息吗?我的意思是没有摘录? - Latenec
谢谢,我本来想写一篇带解释的完整文章,但是因为可能已经有了可以回答这个问题的被接受的答案,所以我放弃了... 我会记下来的 :) - Dominik Bucher
更新了,请在我的回答下面写评论,感谢! - Dominik Bucher
显示剩余2条评论
3个回答

12
如果我理解你的问题正确,你认为@IBOutlet属性应该始终标记为private...好吧,这不是真的。但直接访问属性也不安全。你知道ViewControllers、TableViewCells和这些对象在可选的IBOutlets上使用隐式展开的原因...当你使用storyboards或仅在代码中的某个地方使用它们时,你不需要初始化ViewController...另一种方式 - 想象一下你正在以编程方式创建VC,并将所有标签传递给初始化程序...这会让你头脑爆炸...相反,在storyboard中,你可以这样做:@IBOutlet var myLabel: UILabel! 这很酷,你不需要在init中设置它,它只会等待在访问其值之前的某个时间点被设置...界面构建器会在ViewDidLoad之前处理初始化,所以标签在此之后不会为nil...再次在AwakeFromNib方法进入UITableViewCell子类之前,当你尝试访问你的bookTitle标签属性时,它会崩溃,因为它将为空...这就是为什么这应该是私有的棘手部分...否则,当你知道VC 100%在场景中分配时,没有必要害羞并使一切都是私有的...例如,在prepare(for segue:)方法中工作时,你绝不能访问@IBOutlets。因为它们没有分配,即使它们被分配了,它们也会被一些推送/呈现/其他函数中的内部调用覆盖...好了,那现在该怎么办?当使用UITableViewCell子类时,你可以安全地访问IBOutlets(仅当你使用storyboard并且单元格在你的TableView中❗️),并更改它们的值...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
// We shouldn't return just some constructor with UITableViewCell, but who cares for this purposes...
 guard let cell = tableView.dequeueReusableCell(withIdentifier: "bookTableViewCell", for: indexPath) else { return UITableViewCell() }
cell.bookTitle.text = "any given text" // This should work ok because of interface builder...
}

上述案例应该适用于MVC模式,而不是MVVM或其他模式,其中您不使用storyboard与tableViewControllers和嵌入式单元格太多……(由于注册单元格,但这是另一篇文章……)
我将给你一些指针,如何设置单元格/ViewController中的值,而不触摸实际值,并使其安全……还有一个良好的实践(安全)是使IBOutlets是可选的,以达到100%的安全性,但这并非必要,说实话,这将是解决这个问题的奇怪方法:
ViewControllers:
class SomeVC: UIViewController {

    // This solution should be effective when those labels could be marked weak too...
    // Always access weak variables NOT DIRECTLY but with safe unwrap...
    @IBOutlet var titleLabel: UILabel?
    @IBOutlet var subtitleLabel: UILabel?

    var myCustomTitle: String?
    var myCustomSubtitle: String?

    func setup(with dataSource: SomeVCDataSource ) {

        guard let titleLabel = titleLabel, let subtitleLabel = subtitleLabel else { return }
        // Now the values are safely unwrapped and nothing can crash...
        titleLabel.text = dataSource.title
        subtitleLabel.text = dataSource.subtitle
    }

    // WHen using prepare for segue, use this:
    override func viewDidLoad() {

        super.viewDidLoad()
        titleLabel.text = myCustomTitle
        subtitleLabel.text = myCustomSubtitle

    }

}

struct SomeVCDataSource {

    var title: String
    var subtitle: String
}

下一个问题可能是这样的:
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    guard let destinationVC = segue.destination as? SomeVC else { return }

    let datasource = SomeVCDataSource(title: "Foo", subtitle: "Bar")
    // This sets up cool labels... but the labels are Nil before the segue occurs and even after that, so the guard in setup(with dataSource:) will fail and return...
    destinationVC.setup(with: datasource)

    // So instead of this you should set the properties myCustomTitle and myCustomSubtitle to values you want and then in viewDidLoad set the values
    destinationVC.myCustomTitle = "Foo"
    destinationVC.myCustomSubtitle = "Bar"
}

你知道的,你不需要将你的IBOutlets设置为私有的,因为你永远不知道你将如何使用它们。如果你需要更多例子或有什么不清楚的地方,请随时问...祝你编码愉快,学习深入!


非常感谢您用深入的细节和例子向我解释! - Latenec
2
我提出这个问题是因为我想了解一下安全原因,是否应该将IBOutlets设为私有。其中一位开发者退回了我的代码,因为我的IBOutlet不是私有的,并告诉我应该使用方法来传递数据。如果我这样做,就可以避免别人访问单元格并更改任何单元格属性的情况。 - Latenec
@matt 然后为viewController编写测试,测试其预期行为,而不访问标签并检查其状态。 - Dominik Bucher
是的,这真是遗憾。你会认为@testable可以克服这个问题;不知道是否值得提交一个错误报告呢?为了可测试性而被迫违反良好的隐私实践并不好。 - matt

6

您应该只公开所需内容。

例如,您可以仅在单元格中设置和获取text属性。

class BookTableViewCell: UITableViewCell {
    @IBOutlet weak private var bookTitleLabel: UILabel!

    var bookTitle: String? {
        set {
            bookTitleLabel.text = newValue
        }
        get {
            return bookTitleLabel.text
        }
    }
}

然后,无论你需要:

cell.bookTitle = "It"

现在外部对象无法访问bookTitleLabel,但可以更改其文本内容。
我通常会编写一个配置方法,该方法接收数据对象并私下设置所有输出特性。

非常感谢你的简短解释! - Latenec
@Latenec - 很高兴我能帮到你。 - DanielH
同意对于大型项目而言,限定 IBOutlet 的作用域比仅仅遵循良好实践更好。强制执行良好实践是明智的。 - Mark

0

我还没遇到过将 IBOutlets 设为私有的情况,至少对于单元格来说是这样。如果你想这么做,在你的单元格内提供一个不是私有的 configure 方法,你可以通过这个方法传递你想要赋值给你的 Outlets 的值。你的单元格中的函数可能看起来像这样:

func configure(with bookTitle: String) {
    bookTitle.text = bookTitle
}

编辑:这样的功能在未来可能会很有用,当您更改单元格并添加新的outlet时。然后,您可以向configure函数中添加参数来处理它们。您将在使用该函数的每个地方收到编译器错误,这使得您可以在使用它的任何地方正确设置单元格。在一个重复使用单元格的大型项目中,这非常有帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接