Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 更新超类的tableview_Ios_Swift_Uitableview - Fatal编程技术网

Ios 更新超类的tableview

Ios 更新超类的tableview,ios,swift,uitableview,Ios,Swift,Uitableview,我按照教程制作了MVVP模型tableview 我的tableViewController称为MyProfileController,如下所示: class MyProfileController: UITableViewController { fileprivate var viewModel: ProfileViewModel? override func viewDidLoad() { super.viewDidLoad() table

我按照教程制作了MVVP模型tableview

我的tableViewController称为MyProfileController,如下所示:

class MyProfileController: UITableViewController {

    fileprivate var viewModel: ProfileViewModel?
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UserInfoCell.self, forCellReuseIdentifier: UserInfoCell.identifier)
        viewModel = ProfileViewModel()
        tableView.dataSource = self.viewModel
        }
    }
}
我没有在MyProfileController中定义UITableViewDataSource,而是创建了一个名为ProfileViewModel的视图模型,并将其传递给
tableView.dataSource
。ProfileViewModel的定义如下所示:

class ProfileViewModel: NSObject {
    fileprivate var profile: UserProfile?
    var items = [ProfileViewModelItem]()

    init() {
        super.init()
        //...
    }
}

extension ProfileViewModel: UITableViewDataSource {

    // ...

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserInfoCell
        cell.userDetailTextView.delegate = self
        return cell
    }

    // ...
}

extension ProfileViewModel: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        print(textView.text)

////////////////
// ERROR HERE //
//        tableView.beginUpdates()
//        tableView.endUpdates()
////////////////
    }
}

我在
cellForRowAt indexPath方法
内将委托设置为UITextView,以便当用户在textview中键入时调用
textviewdichange
delegate方法。在这一点上是可行的。问题是我无法从这里更新tableView。如何更新MyProfileController的tablView?

有很多方法可以做到这一点。这取决于你们团队的编码模式规则或者我们应该称之为什么

但这是我通常做的:视图模型有一个用于重新加载数据的协议。或者更好的是,所有my view模型的协议都有一个用于此类重载数据的基类,如下所示:

protocol ProfileDelegate: BaseViewModelDelegate {

}

class ProfileViewModel: NSObject {
    //....
}
下面是BaseViewModelDelegate

/// The Base Delegate of all ViewModels.
protocol BaseViewModelDelegate: NSObjectProtocol {
    /// Ask the controller to reload the data.
    func reloadTableView()
    /// Presents an alert/
    func presentAlert(title: String, message: String, okayButtonTitle: String, cancelButtonTitle: String?, withBlock completion: LLFAlertCallBack?)
}

如您所见,有一个
reloadTableView
方法。如果需要,我可以在这里重新加载控制器的tableView。但是,有很多方法可以做到这一点。我希望这有帮助

您可以使用闭包向表视图控制器发送消息

在数据源对象中声明闭包变量

class ProfileViewModel: NSObject {

    var textViewDidChange: (() -> Void)?

    // If you need to send some data to your controller, declare it with type. In your case it's string.
    // var textViewDidChange: ((String) -> Void)?

}
将消息从文本字段委托发送到新创建的变量,如下所示

func textViewDidChange(_ textView: UITextView) {

    self.textViewDidChange?()
    // If you need to send your string, do it like this
    // self.textViewDidChange?(textView.text)

}
正如您所猜测的,您的变量
textViewDidChange
仍然是
nil
,因此不会传递任何消息。所以我们现在应该宣布

在可以访问数据源的视图控制器中,设置闭包的值

class MyProfileController: UITableViewController {

    fileprivate var viewModel: ProfileViewModel?
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UserInfoCell.self, forCellReuseIdentifier: UserInfoCell.identifier)
        viewModel = ProfileViewModel()
        tableView.dataSource = self.viewModel

        // Set the closure value here

        viewmodel.textViewDidChange = { [unowned self](/* if you are sending data, capture it here, if not leave empty */) in

            // Do whatever you like with your table view here.
            // [unowned self] could sound tricky. It's whole another subject which isn't in the scope of this question. But of course there are some great answers here about it. Simply put, if you don't use it, you'll create a memory leak.

        }

    }

}

您可以将数据源从视图控制器中取出,但遵循正确的分离非常重要,我建议使用这种方法,因为它可以帮助您进行测试

使用协议定义视图模型的行为(为了进行测试,可以使用实现此协议的模拟视图模型):

然后使用以下数据实现viewModel:

class ProfileVieModel: ProfileViewModelType {
    var items = [ProfileViewModelItem]()
    //...
}
然后将视图模型注入数据源对象,并使用它填充表视图和管理所有回调:

class ProfileTableViewDataSource: NSObject, UITableViewDataSource {

    private var viewModel: ProfileViewModelType!

    init(viewModel: ProfileViewModelType) {
        self.viewModel = viewModel
    }

    func textViewDidChange(_ textView: UITextView) {
        print(textView.text)
        viewModel.textViewDidChange?(textView)
    }
}
最后,在视图控制器中,您可以观察视图模型回调并在那里管理您的操作:

class YourViewController: UIViewController {
    private var dataSource: ProfileTableViewDataSource?
    private var viewModel: ProfileViewModelType = ProfileViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = ProfileTableViewDataSource(viewModel: viewModel)
        tableView.dataSource = dataSource 
        bindViewModel()
    }

    func bindViewModel() {
        viewModel.textViewDidChange = { [weak self] textView in 
            // ...
        }
    }
}

与您的问题无关,但我不会将我的数据源对象命名为
model
。您的模型是
var items
的内容,但即使在其中,我也不会使用单词model。@yun-c-han:您的tableview数据源和委托应该是您的视图控制器,而不是视图模型,您的视图控制器可以向视图模型请求项目数组,或从其视图模型中获取indexPath的特定项目,并在
CellForRowatineXpath
中配置单元格。所有UI操作都应由视图控制器负责,并且只需准备数据、业务逻辑、,数据解析可以是viewModel@SandeepBhandari
您的tableview数据源和代理应该是您的视图控制器
。不,这是非常错误的。他所做的是使用表视图控制器的完美方式。让每个班级做一件事,并且只做一件事。此外,您还被单词model弄糊涂了。它是一个数据源对象而不是一个模型。@Desdenova您建议如何为textviewdelegate使用闭包?嗯。。我还不太清楚。如果我定义了一个协议
BaseViewModelDelegate
,我应该如何更改
扩展配置文件ViewModel:UITextViewDelegate
?我如何告诉ProfileViewModel类使用baseViewModelDelegate?到目前为止,您对代码所做的操作是正确的。但是,既然您在问profileViewModel如何告诉ProffileController重新加载tableView,那么就看这里了。在viewModel中,调用
reloadTableView()
delegate方法,如:
self.delegate?.reloadTableView()
,然后符合该协议的profileController将接收它。在此之前,您需要了解委托模式:)如果我稍后有时间,我将给您一个示例项目。这很酷!我将进一步研究闭包函数。谢谢@Desdenova!
class YourViewController: UIViewController {
    private var dataSource: ProfileTableViewDataSource?
    private var viewModel: ProfileViewModelType = ProfileViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = ProfileTableViewDataSource(viewModel: viewModel)
        tableView.dataSource = dataSource 
        bindViewModel()
    }

    func bindViewModel() {
        viewModel.textViewDidChange = { [weak self] textView in 
            // ...
        }
    }
}