![](/img/trans.png)
[英]How to update the contentSize of UIScrollView in a parent controller from a UITableView in Child viewController
[英]UITableView with controller separate from ViewController
我是Swift和XCode的新手,今年夏天参加了iOS开发课程。 我们正在做的许多项目以及我看到的针对PickerViews,TableViews等UI元素的示例都在ViewController.swift文件中定义了充当主视图控制器的所有内容。 这很好用,但是我开始达到项目复杂性的地步,我真的希望我的所有代码都不会被挤到同一个Swift文件中。 我已经和一位在旁进行iOS开发的朋友交谈过,他说这是理智而合理的,并且与适当的面向对象的编程保持一致……但是我似乎无法使其正常工作。 经过反复试验,我得到了这种情况:该应用程序在模拟器中运行,出现了UITableView,但是我没有在其中填充条目。 当所有代码都在ViewController中时,我可以使它正常工作,但是一旦我开始尝试创建一个新的控制器类并将该类的实例作为UITableView的dataSource / delegate时,我什么都没得到。 我觉得我在这里缺少对Swift的一些核心了解,或者在XCode中使用Interface Builder做了一些错误。
我的最终结果应该是其中包含三个条目的UITableView。 目前,我正在获取没有条目的UITableView。 我将跟着一些我已经Google搜索过的不同示例,但主要是另一个SO问题: Swift的UITableView示例
ViewController.swift:
import UIKit
class ViewController: UIViewController{
@IBOutlet var stateTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
var viewController = StateViewController()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
}
StateViewController.swift:
import UIKit
class StateViewController: UITableViewController{
var states = ["Indiana", "Illinois", "Nebraska"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return states.count;
}
func tableView(cellForRowAttableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"cell")
cell.textLabel?.text = states[indexPath.row]
return cell
}
}
在XCode中,我将UITableView连接到了View Controller; 出口设置为dataSource
和delegate
,引用出口为stateTableView
。
我没有任何错误; 我确实在ViewController.swift中的`var viewController = StateViewController()'语句上得到警告,在该语句中我希望我使用一个常量,但是将其切换为常量不会改变行为(这是应该的,我假设)。
最初,我以为该错误是在StateViewController.swift文件中,我没有在该文件中创建符合UITableViewDataSource或UITableViewDelegate协议的对象,但是如果我将它们添加到类语句中,我会立即收到诸如“冗余一致性'StateViewController'到协议'UITableViewDataSource''“-我正在读这是因为从UITableViewController继承也自动继承了其他协议。
我尝试的最后一件事是在StateViewController的tableView函数中引用self.states
,但是我敢肯定,Swift中的self的工作方式与Python中的self.states相同,并且感觉就像我只是在尝试添加魔术字一样点。
就我目前有限的知识斯威夫特能带我调查过,所以任何回答,解释我做错了什么 ,而不是仅仅告诉我要解决什么将是非常赞赏。
您的问题是由内存管理问题引起的。 您有以下代码:
override func viewDidLoad() {
super.viewDidLoad()
var viewController = StateViewController()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
考虑一下viewController
变量的生命周期。 当到达viewDidLoad
的末尾时,它结束。 并且由于表视图的dataSource
和delegate
属性weak
,因此在viewDidLoad
结束后,没有强有力的参考来使StateViewController
保持活动状态。 由于引用弱,结果是在到达viewDidLoad
的末尾后,表视图的dataSource
和delegate
属性恢复为nil
。
解决方案是创建对StateViewController
的强引用。 通过向视图控制器类添加属性来执行此操作:
class ViewController: UIViewController{
@IBOutlet var stateTableView: UITableView!
let viewController = StateViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
}
现在您的代码可以使用了。
一旦完成该工作,请查看Ahmed F的答案。绝对没有理由将StateViewController
类用作视图控制器。 从任何意义上说,它都不是视图控制器。 它只是一个实现表视图数据源和委托方法的类。
虽然我觉得它更易于阅读和理解以实现在同一视图 - 控制数据源/委托方法,什么是你想才达到同样有效。 然而, StateViewController
类并不一定是一个子类UITableViewController
(我认为这是你误解它的一部分),例如(改编自另一个答案对我来说):
import UIKit
// ViewController File
class ViewController: UIViewController {
var handler: Handler!
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
handler = Handler()
tableView.dataSource = handler
}
}
处理程序类:
import UIKit
class Handler:NSObject, UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell")
cell?.textLabel?.text = "row #\(indexPath.row + 1)"
return cell!
}
}
您试图将数据源和UITableView的委托设置为UITableViewController
。 正如@Ahmad在同一个类(即ViewController)中提到的那样,它更易于理解,您可以采用清晰的方法将数据源和UITableView
委托与UIViewController
分开。 您可以优先使用NSObject的子类,并将其用作UITableView
数据源和delgate类。
您还可以使用container view
并嵌入UITableViewController
。 您所有的表格视图代码都将移至UITableViewController子类,因此将表格视图逻辑与View Controller分开
希望能帮助到你。 快乐编码!
您还可以使用适配器通过超级干净的代码来解决此问题,并且易于理解,例如
protocol MyTableViewAdapterDelegate: class {
func myTableAdapter(_ adapter:MyTableViewAdapter, didSelect item: Any)
}
class MyTableViewAdapter: NSObject {
private let tableView:UITableView
private weak var delegate:MyTableViewAdapterDelegate!
var items:[Any] = []
init(_ tableView:UITableView, _ delegate:MyTableViewAdapterDelegate) {
self.tableView = tableView
self.delegate = delegate
super.init()
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func setData(data:[Any]) {
self.items = data
reloadData()
}
func reloadData() {
tableView.reloadData()
}
}
extension MyTableViewAdapter: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Hi im \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
delegate?.myTableAdapter(self, didSelect: items[indexPath.row])
}
}
使用 即插即用
class ViewController: UIViewController, MyTableViewAdapterDelegate {
@IBOutlet var stateTableView: UITableView!
var myTableViewAdapter:MyTableViewAdapter!
override func viewDidLoad() {
super.viewDidLoad()
myTableViewAdapter = MyTableViewAdapter(stateTableView, self)
}
func myTableAdapter(_ adapter: MyTableViewAdapter, didSelect item: Any) {
print(item)
}
}
在项目中分离这些问题的方式是,通过创建一个类来跟踪应用程序的状态并对数据执行所需的操作。 此类负责获取实际数据(创建硬编码或从持久性存储获取数据)。 这是一个真实的例子:
import Foundation
class CountriesStateController {
private var countries: [Country] = [
Country(name: "United States", visited: true),
Country(name: "United Kingdom", visited: false),
Country(name: "France", visited: false),
Country(name: "Italy", visited: false),
Country(name: "Spain", visited: false),
Country(name: "Russia", visited: false),
Country(name: "Moldova", visited: false),
Country(name: "Romania", visited: false)
]
func toggleVisitedCountry(at index: Int) {
guard index > -1, index < countries.count else {
fatalError("countryNameAt(index:) - Error: index out of bounds")
}
let country = countries[index]
country.visited = !country.visited
}
func numberOfCountries() -> Int {
return countries.count
}
func countryAt(index: Int) -> Country {
guard index > -1, index < countries.count else {
fatalError("countryNameAt(index:) - Error: index out of bounds")
}
return countries[index]
}
}
然后,我创建实现UITableViewDataSource和UITableViewDelegate协议的单独的类:
import UIKit
class CountriesTableViewDataSource: NSObject {
let countriesStateController: CountriesStateController
let tableView: UITableView
init(stateController: CountriesStateController, tableView: UITableView) {
countriesStateController = stateController
self.tableView = tableView
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
super.init()
self.tableView.dataSource = self
}
}
extension CountriesTableViewDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return the number of items in the section(s)
return countriesStateController.numberOfCountries()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// return a cell of type UITableViewCell or another subclass
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
let country = countriesStateController.countryAt(index: indexPath.row)
let countryName = country.name
let visited = country.visited
cell.textLabel?.text = countryName
cell.accessoryType = visited ? .checkmark : .none
return cell
}
}
import UIKit
protocol CountryCellInteractionDelegate: NSObjectProtocol {
func didSelectCountry(at index: Int)
}
class CountriesTableViewDelegate: NSObject {
weak var interactionDelegate: CountryCellInteractionDelegate?
let countriesStateController: CountriesStateController
let tableView: UITableView
init(stateController: CountriesStateController, tableView: UITableView) {
countriesStateController = stateController
self.tableView = tableView
super.init()
self.tableView.delegate = self
}
}
extension CountriesTableViewDelegate: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected row at index: \(indexPath.row)")
tableView.deselectRow(at: indexPath, animated: false)
countriesStateController.toggleVisitedCountry(at: indexPath.row)
tableView.reloadRows(at: [indexPath], with: .none)
interactionDelegate?.didSelectCountry(at: indexPath.row)
}
}
这就是现在从ViewController类中使用它们很容易:
import UIKit
class ViewController: UIViewController, CountryCellInteractionDelegate {
public var countriesStateController: CountriesStateController!
private var countriesTableViewDataSource: CountriesTableViewDataSource!
private var countriesTableViewDelegate: CountriesTableViewDelegate!
private lazy var countriesTableView: UITableView = createCountriesTableView()
func createCountriesTableView() -> UITableView {
let tableViewOrigin = CGPoint(x: 0, y: 0)
let tableViewSize = view.bounds.size
let tableViewFrame = CGRect(origin: tableViewOrigin, size: tableViewSize)
let tableView = UITableView(frame: tableViewFrame, style: .plain)
return tableView
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard countriesStateController != nil else {
fatalError("viewDidLoad() - Error: countriesStateController was not injected")
}
view.addSubview(countriesTableView)
configureCountriesTableViewDelegates()
}
func configureCountriesTableViewDelegates() {
countriesTableViewDataSource = CountriesTableViewDataSource(stateController: countriesStateController, tableView: countriesTableView)
countriesTableViewDelegate = CountriesTableViewDelegate(stateController: countriesStateController, tableView: countriesTableView)
countriesTableViewDelegate.interactionDelegate = self
}
func didSelectCountry(at index: Int) {
let country = countriesStateController.countryAt(index: index)
print("Selected country: \(country.name)")
}
}
请注意,ViewController并未创建countryStateController对象,因此必须将其注入。 我们可以从Flow Controller,Coordinator或Presenter等中进行操作。我是从AppDelegate进行的,如下所示:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let countriesStateController = CountriesStateController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if let viewController = window?.rootViewController as? ViewController {
viewController.countriesStateController = countriesStateController
}
return true
}
/* ... */
}
如果从来没有注入过它-我们会发生崩溃时间崩溃,因此我们知道我们必须立即修复它。
这是国家(地区)类:
import Foundation
class Country {
var name: String
var visited: Bool
init(name: String, visited: Bool) {
self.name = name
self.visited = visited
}
}
注意ViewController类的干净程度和苗条程度。 它少于50行,并且如果从Interface Builder创建表视图-它将变小8-9行。
上面的ViewController可以完成它应该做的事情,并且可以作为View和Model对象之间的中介。 该表是否显示一种类型还是多种类型的单元格并不重要,因此用于注册单元格的代码属于CountrysTableViewDataSource类,该类负责根据需要创建每个单元格。
有些人将CountrysTableViewDataSource和CountrysTableViewDelegate结合在一个类中,但是我认为这违反了单一职责原则。 这两个类都需要访问相同的DataProvider / State Controller对象,而ViewController也需要访问该对象。
请注意,View Controller现在可以知道何时调用didSelectRowAt,因此我们需要在UITableViewDelegate内部创建一个附加协议:
protocol CountryCellInteractionDelegate: NSObjectProtocol {
func didSelectCountry(at index: Int)
}
而且我们还需要一个委托属性来使通信成为可能:
weak var interactionDelegate: CountryCellInteractionDelegate?
请注意,无论是CountrysTableViewDataSource还是CountrysTableViewDelegate类都不知道ViewController类的存在。 使用面向协议的编程-我们甚至可以消除这两个类与CountrysStateController类之间的紧密耦合。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.