iOS TableView,不带Storyboard的UITableView
今天,我们将在不使用Storyboard的情况下在我们的应用程序中实现iOS TableView UITableView类。
我们在这里使用Objective-C中的Storyboard实现了UITableView。
在本教程中,我们将使用Swift 3。
iOS TableView UITableView
与Swift 2.3相比,Swift 3中有许多主要代码更改。
对于来说,这只是我们将来所有iOS教程中都会使用的另一种语言。
但是,如果您尚未迁移到Swift 3,我将着重介绍本教程中的重大变化。
iOS TableView入门
首先,首先创建一个New Project,然后选择模板作为Single View Application。
在以下屏幕中,输入相关的应用程序和捆绑包标识符名称。
示例如下所示。
项目准备就绪后,选择Main.storyboard文件并将其删除(移至回收站)。
完成此操作后,让我们在模拟器上运行一个空项目,看看一切都很好。
观看右下角的日志中的错误将显示为:由于未捕获的异常'NSInvalidArgumentException',正在终止应用程序,原因:'在捆绑包NSBundle中找不到名为'Main'的故事板。
我们需要从项目配置中删除情节提要参考。
为此,转到" Info.plist",然后在"主要情节提要"文件基本名称字段中设置一个空值。
现在,我们无需编写故事板就可以编写应用程序代码了。
我们选择" ViewController.swift"并开始编码。
iOS UITableView示例代码
现在我们已经删除了Main.storyboard,ViewController.swift已取消链接。
我们需要找到一种方法使其成为应用程序的根视图控制器,而无需创建另一个故事板。
AppDelegate.swift文件可以帮助我们。
AppDelegate类可确保您的应用与系统和其他应用正确交互。
我们通过以下方式覆盖了函数application:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window!.backgroundColor = UIColor.white window!.rootViewController = ViewController() window!.makeKeyAndVisible() //Override point for customization after application launch. return true }
这使ViewController成为根视图控制器,并在我们的应用程序中启动第一个ViewController。
让我们跳到ViewController.swift文件,在该文件中将以编程方式显示和自定义UITableView。
首先,在ViewController.swift类中实现UITableViewDelegate和UITableViewDataSource协议,如下所示:
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { } }
以上三个函数需要强制执行,否则会出现编译时错误。
Swift 3注意:现在,函数参数始终包含第一个参数的标签。
本质上,函数名称的最后一部分已移动为第一个参数的名称。
例如:
//Swift 2.3 override func numberOfSectionsInTableView(tableView: UITableView) -> Int //Swift 3 override func numberOfSections(in tableView: UITableView) -> Int
除了Swift 3之外,由于它们是显而易见的,因此省略了函数中不必要的词。
回到代码,我们需要初始化并在视图中添加UITableView。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView = UITableView() var tableData = ["Beach", "Clubs", "Chill", "Dance"] override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.register(UITableViewCell.self, forCellReuseIdentifier: "my") view.addSubview(tableView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "my", for: indexPath) cell.textLabel?.text = "This is row \(tableData[indexPath.row])" return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData.count } }
TableView的行是从tableData数组填充的。
TableView样式也可以设置为" UITableViewStyle.grouped"。
运行上述代码时的输出如下所示。
需要改进的地方:我们需要在视图和状态列之间添加填充。
同样,我们必须从空单元格中删除不必要的行分隔符。
要添加填充,请在viewDidLoad方法中添加以下行。
tableView.contentInset.top = 20
要删除空单元格和分隔线,我们需要包装TableView的高度,直到非空行数为止,并在底部添加页脚。
包括以上两个实现后的ViewController.swift代码如下。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView = UITableView() var tableData = ["Beach", "Clubs", "Chill", "Dance"] override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.register(UITableViewCell.self, forCellReuseIdentifier: "my") tableView.contentInset.top = 20 let contentSize = self.tableView.contentSize let footer = UIView(frame: CGRect(x: self.tableView.frame.origin.x, y: self.tableView.frame.origin.y + contentSize.height, width: self.tableView.frame.size.width, height: self.tableView.frame.height - self.tableView.contentSize.height)) self.tableView.tableFooterView = footer view.addSubview(tableView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "my", for: indexPath) cell.textLabel?.text = "This is row \(tableData[indexPath.row])" return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData.count } }
上面的ViewController实现的输出如下。
上面的输出中有一个滚动问题。
滚动时的TableView与状态列重叠,而不是在其下方。
让我们通过如下设置TableView的边界来解决此问题。
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height let displayWidth: CGFloat = self.view.frame.width let displayHeight: CGFloat = self.view.frame.height tableView.frame = CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight)
将其添加到ViewController.swift文件中将产生以下代码并输出。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView = UITableView() var tableData = ["Beach", "Clubs", "Chill", "Dance"] override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.register(UITableViewCell.self, forCellReuseIdentifier: "my") let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height let displayWidth: CGFloat = self.view.frame.width let displayHeight: CGFloat = self.view.frame.height tableView.contentInset.top = 20 tableView.frame = CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight) let contentSize = self.tableView.contentSize let footer = UIView(frame: CGRect(x: self.tableView.frame.origin.x, y: self.tableView.frame.origin.y + contentSize.height, width: self.tableView.frame.size.width, height: self.tableView.frame.height - self.tableView.contentSize.height)) self.tableView.tableFooterView = footer view.addSubview(tableView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "my", for: indexPath) cell.textLabel?.text = "This is row \(tableData[indexPath.row])" return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData.count } }
我们添加一项功能,以便选择任何行都会显示一个警报对话框。
我们需要覆盖函数didSelectRowAt
来使每个都可选。
具有上述实现的ViewController.swift的代码如下。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView = UITableView() var tableData = ["Beach", "Clubs", "Chill", "Dance"] override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.register(UITableViewCell.self, forCellReuseIdentifier: "my") let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height let displayWidth: CGFloat = self.view.frame.width let displayHeight: CGFloat = self.view.frame.height tableView.contentInset.top = 20 tableView.frame = CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight) let contentSize = self.tableView.contentSize let footer = UIView(frame: CGRect(x: self.tableView.frame.origin.x, y: self.tableView.frame.origin.y + contentSize.height, width: self.tableView.frame.size.width, height: self.tableView.frame.height - self.tableView.contentSize.height)) self.tableView.tableFooterView = footer view.addSubview(tableView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "my", for: indexPath) cell.textLabel?.text = "This is row \(tableData[indexPath.row])" return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData.count } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell showDialog(text: (currentCell.textLabel?.text)!) } func showDialog(text : String) { let alert = UIAlertController(title: "Alert", message: text, preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } }
tableView.deselectRow(at:indexPath,animation:true)
用于在不再按下时从选定行中删除突出显示。
具有多个节的UITableView
让我们自定义UITableView,使其包含多个部分。
我们需要重写下面显示的另外两个函数。
func numberOfSections(in tableView: UITableView) -> Int { } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { }
下面给出了具有多个部分的ViewController.swift。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView = UITableView() var tableData = ["Beach", "Clubs", "Chill", "Dance"] let data = [["0,0", "0,1", "0,2"], ["1,0", "1,1", "1,2"]] let headerTitles = ["Some Data 1", "Some Data 2"] override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.register(UITableViewCell.self, forCellReuseIdentifier: "my") let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height let displayWidth: CGFloat = self.view.frame.width let displayHeight: CGFloat = self.view.frame.height tableView.contentInset.top = 20 tableView.frame = CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight) let contentSize = self.tableView.contentSize let footer = UIView(frame: CGRect(x: self.tableView.frame.origin.x, y: self.tableView.frame.origin.y + contentSize.height, width: self.tableView.frame.size.width, height: self.tableView.frame.height - self.tableView.contentSize.height)) self.tableView.tableFooterView = footer view.addSubview(tableView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "my", for: indexPath) //cell.textLabel?.text = "This is row \(tableData[indexPath.row])" cell.textLabel?.text = "This is row \(data[indexPath.section][indexPath.row])" return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //return tableData.count return data[section].count } func numberOfSections(in tableView: UITableView) -> Int { return data.count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if section < headerTitles.count { return headerTitles[section] } return nil } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell showDialog(text: (currentCell.textLabel?.text)!) } func showDialog(text : String) { let alert = UIAlertController(title: "Alert", message: text, preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } }
上面代码的输出如下。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView = UITableView() var tableData = ["Beach", "Clubs", "Chill", "Dance"] let data = [["0,0", "0,1", "0,2"], ["1,0", "1,1", "1,2"]] let headerTitles = ["Some Data 1", "Some Data 2"] override func viewDidLoad() { super.viewDidLoad() //Do any additional setup after loading the view, typically from a nib. tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.register(UITableViewCell.self, forCellReuseIdentifier: "my") let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height let displayWidth: CGFloat = self.view.frame.width let displayHeight: CGFloat = self.view.frame.height tableView.contentInset.top = 20 tableView.frame = CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight) let contentSize = self.tableView.contentSize let footer = UIView(frame: CGRect(x: self.tableView.frame.origin.x, y: self.tableView.frame.origin.y + contentSize.height, width: self.tableView.frame.size.width, height: self.tableView.frame.height - self.tableView.contentSize.height)) self.tableView.tableFooterView = footer view.addSubview(tableView) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() //Dispose of any resources that can be recreated. } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "my", for: indexPath) //cell.textLabel?.text = "This is row \(tableData[indexPath.row])" cell.textLabel?.text = "This is row \(data[indexPath.section][indexPath.row])" return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //return tableData.count return data[section].count } func numberOfSections(in tableView: UITableView) -> Int { return data.count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if section < headerTitles.count { return headerTitles[section] } return nil } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell showDialog(text: (currentCell.textLabel?.text)!) } func showDialog(text : String) { let alert = UIAlertController(title: "Alert", message: text, preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil)) self.present(alert, animated: true, completion: nil) } func scrollViewDidScroll(_ scrollView: UIScrollView) { let sectionHeight: CGFloat = 80 if scrollView.contentOffset.y = sectionHeight { scrollView.contentInset = UIEdgeInsetsMake(-sectionHeight, 0, 0, 0) } } }