[英]Swift 5 - make tableview wait until data from api call comes back (using multiple tableviews)
Issue: Fatal error when View is loading.问题:加载视图时出现致命错误。 I know the problem is because there is no data available when to table view is trying to load.
我知道问题是因为在尝试加载表视图时没有可用数据。 But, because I am using multiple TableViews in one UI I have to force the cell return in CellForRowAt.
但是,因为我在一个 UI 中使用了多个 TableView,所以我必须在 CellForRowAt 中强制返回单元格。
Is there a better way of setting up different data for each TableView?有没有更好的方法为每个 TableView 设置不同的数据?
THANKS FOR THE HELP!谢谢您的帮助!
import UIKit
class NewCustomTaskVC: UIViewController {
@IBAction func CancelPressed (_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBOutlet weak var taskTypeSelectionBtn: UIButton!
@IBOutlet weak var FrameSelectionBtn: UIButton!
@IBOutlet weak var AssignSelectionBtn: UIButton!
@IBAction func SelecttaskTypePressed(_ sender: Any) {
if tableViewTaskType.isHidden {
self.tableViewTaskType.isHidden = false
self.tableViewTaskType.rowHeight = 43.5
} else {
self.tableViewTaskType.isHidden = true
}
}
@IBAction func SelectFramePressed(_ sender: Any) {
if tableViewFrame.isHidden {
self.tableViewFrame.isHidden = false
} else {
self.tableViewFrame.isHidden = true
}
}
@IBAction func SelectAssignToPressed(_ sender: Any) {
if tableViewAssignTo.isHidden {
self.tableViewAssignTo.isHidden = false
} else {
self.tableViewAssignTo.isHidden = true
}
}
@IBOutlet weak var tableViewTaskType: UITableView!
@IBOutlet weak var tableViewFrame: UITableView!
@IBOutlet weak var tableViewAssignTo: UITableView!
var cellID = ""
var array = ["String", "Test", "Next","Test 2", "Test 3"]
override func viewDidLoad() {
super.viewDidLoad()
getData()
tableViewTaskType.isHidden = true
tableViewFrame.isHidden = true
tableViewAssignTo.isHidden = true
tableViewTaskType.delegate = self
tableViewFrame.delegate = self
tableViewAssignTo.delegate = self
tableViewTaskType.dataSource = self
tableViewFrame.dataSource = self
tableViewAssignTo.dataSource = self
self.tableViewTaskType.register(UITableViewCell.self, forCellReuseIdentifier: "cell1")
self.tableViewFrame.register(UITableViewCell.self, forCellReuseIdentifier: "cell2")
self.tableViewAssignTo.register(UITableViewCell.self, forCellReuseIdentifier: "cell3")
}
func getData () {
//dispatchGroup.enter()
var count = 0
APICallBack.getFramesData(completion: { success in
if success == true {
print("frames success")
count += 1
} })
APICallBack.getTaskTypeData { success in
if success == true {
print("task success")
count += 1
}
}
APICallBack.GETUserData(completion: { success in
if success == true {
print("user success")
count += 1
} })
if count == 3{
DispatchQueue.main.async {
self.tableViewTaskType.reloadData()
self.tableViewAssignTo.reloadData()
self.tableViewFrame.reloadData()
print("ALL COMPLETE")
}
}
}
}
extension NewCustomTaskVC : UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = 1
switch tableView {
case tableViewTaskType:
count = TaskTypeData.typeModel.count
case tableViewFrame:
count = FramesData.framesModel.count
case tableViewAssignTo:
count = CustomerData.customerModel.count
default:
print("none")
return count
}
//return 5
return count
}
**PROBLEM IS HERE
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
if tableView == self.tableViewTaskType{
cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
cell!.textLabel!.text = TaskTypeData.typeModel[indexPath.row].TaskTypeName
// cell!.textLabel?.text = array[indexPath.row]
}
if tableView == tableViewFrame{
cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath)
cell!.textLabel!.text = FramesData.framesModel[indexPath.row].FrameName
// cell!.textLabel?.text = array[indexPath.row]
}
if tableView == self.tableViewAssignTo {
cell = tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath)
cell!.textLabel!.text = UserData.userModel[indexPath.row].UserFirst
// cell.textLabel?.text = array[indexPath.row]
}
// let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
// cell.textLabel?.text = array[indexPath.row]
return cell!
}
** TO HERE!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = array[indexPath.row]
print(item)
tableViewTaskType.isHidden = true
}
}
My UI View:我的用户界面视图:
The error I get if I don't force it:如果我不强制它,我得到的错误:
I assume that you want to display an empty "placeholder" cell if the data source array is empty.如果数据源数组为空,我假设您想显示一个空的“占位符”单元格。 You need to check explicitly for that condition in your
cellForRow
function.您需要在
cellForRow
function 中明确检查该条件。
As far as co-ordinating the fetch from multiple API endpoints, you can use a DispatchGroup
- Some commented code indicates you may have tried this.至于协调从多个 API 端点的提取,您可以使用
DispatchGroup
- 一些注释代码表明您可能已经尝试过。
override func viewDidLoad() {
super.viewDidLoad()
tableViewTaskType.isHidden = true
tableViewFrame.isHidden = true
tableViewAssignTo.isHidden = true
tableViewTaskType.delegate = self
tableViewFrame.delegate = self
tableViewAssignTo.delegate = self
tableViewTaskType.dataSource = self
tableViewFrame.dataSource = self
tableViewAssignTo.dataSource = self
self.tableViewTaskType.register(UITableViewCell.self, forCellReuseIdentifier: "cell1")
self.tableViewFrame.register(UITableViewCell.self, forCellReuseIdentifier: "cell2")
self.tableViewAssignTo.register(UITableViewCell.self, forCellReuseIdentifier: "cell3")
getData()
}
func getData () {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
APICallBack.getFramesData(completion: { success in
if success == true {
print("frames success")
}
dispatchGroup.leave()
})
APICallBack.getTaskTypeData { success in
if success == true {
print("task success")
}
dispatchGroup.leave()
}
APICallBack.GETUserData(completion: { success in
if success == true {
print("user success")
}
dispatchGroup.leave()
})
dispatchGroup.notify {
self.tableViewTaskType.reloadData()
self.tableViewAssignTo.reloadData()
self.tableViewFrame.reloadData()
print("ALL COMPLETE")
}
}
extension NewCustomTaskVC : UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch tableView {
case tableViewTaskType:
return max(1,TaskTypeData.typeModel.count)
case tableViewFrame:
return max(1,FramesData.framesModel.count)
case tableViewAssignTo:
return max(1,CustomerData.customerModel.count)
default:
fatalError("Unexpected table view")
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch tableView {
case self.tableViewTaskType:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
if !TaskTypeData.typeModel.isEmpty {
cell.textLabel!.text = TaskTypeData.typeModel[indexPath.row].TaskTypeName
}
return cell
case tableViewFrame:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath)
if !FramesData.framesModel.isEmpty {
cell!.textLabel!.text = FramesData.framesModel[indexPath.row].FrameName
}
return cell
case self.tableViewAssignTo:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath)
if !UserData.userModel.isEmpty {
cell!.textLabel!.text = UserData.userModel[indexPath.row].UserFirst
}
return cell
default:
fatalError("Unexpected Tableview")
}
}
you can set tableView.dataSource & tableView.delegate to self when your data is back当您的数据返回时,您可以将 tableView.dataSource 和 tableView.delegate 设置为 self
There is multiple problems with your code.您的代码存在多个问题。
1) You call get data before your table view had registered it cells. 1)您在表格视图注册它的单元格之前调用获取数据。 So if your API would load data immediately table view will be call dataSource methods
因此,如果您的 API 会立即加载数据,则表视图将调用 dataSource 方法
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
but since the但由于
self.tableViewTaskType.register(UITableViewCell.self, forCellReuseIdentifier: "cell1")
self.tableViewFrame.register(UITableViewCell.self, forCellReuseIdentifier: "cell2")
self.tableViewAssignTo.register(UITableViewCell.self, forCellReuseIdentifier: "cell3")
called in the end of viewDidLoad, your would get crash when you dequeue your cells in cellForAtIndexPath method.在 viewDidLoad 结束时调用,当您在 cellForAtIndexPath 方法中将单元格出列时,您会崩溃。
The solution is to move getData call to the end of viewDidLoad method.解决方案是将 getData 调用移至 viewDidLoad 方法的末尾。
2) If you want to display all tables data at one time (when API is complete loading getFramesData, getTaskTypeData and GETUserData) you would need to synchronise this callbacks. 2) 如果要一次显示所有表数据(当 API 完成 getFramesData、getTaskTypeData 和 GETUserData 加载时),则需要同步此回调。 You could do this with DispatchGroup.
您可以使用 DispatchGroup 执行此操作。
func getData () {
let apiDispatchGroup = DispatchGroup()
APICallBack.getFramesData { success in
apiDispatchGroup.leave()
}
apiDispatchGroup.enter()
APICallBack.getTaskTypeData { success in
apiDispatchGroup.leave()
}
apiDispatchGroup.enter()
APICallBack.GETUserData { success in
apiDispatchGroup.leave()
}
apiDispatchGroup.enter()
apiDispatchGroup.notify(queue: DispatchQueue.main) {
self.tableViewTaskType.reloadData()
self.tableViewAssignTo.reloadData()
self.tableViewFrame.reloadData()
}
}
3) It's not good idea to use one dataSOurce class for mutltiple UITableView, because of dataSource become god object. 3) 对多个 UITableView 使用一个 dataSOurce class 不是一个好主意,因为 dataSource 成为神 object。 The better approach is to use one ContainerViewController that contains three child UITableViewController, and pass data to the childs when data has loaded from the API.
更好的方法是使用一个包含三个子 UITableViewController 的 ContainerViewController,并在从 API 加载数据时将数据传递给子节点。
It is perfectly fine to have multiple view controllers in one screen.在一个屏幕上拥有多个视图控制器是非常好的。 So I suggest that you create three view controllers, one for each table view.
所以我建议你创建三个视图控制器,每个表视图一个。 And each table view has its own datasource.
每个表视图都有自己的数据源。 Then use a custom container view controller as described here: https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
然后使用自定义容器视图 controller,如下所述: https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
Try to reload TableView data this way,尝试以这种方式重新加载 TableView 数据,
func getData () {
//dispatchGroup.enter()
var count = 0
APICallBack.getFramesData(completion: { success in
if success == true {
print("frames success")
count += 1
self.reloadTableViewsIfNeeded(count)
} })
APICallBack.getTaskTypeData { success in
if success == true {
print("task success")
count += 1
self.reloadTableViewsIfNeeded(count)
}
}
APICallBack.GETUserData(completion: { success in
if success == true {
print("user success")
count += 1
self.reloadTableViewsIfNeeded(count)
} })
}
func reloadTableViewsIfNeeded (_ conunt: Int) {
if count == 3{
DispatchQueue.main.async {
self.tableViewTaskType.reloadData()
self.tableViewAssignTo.reloadData()
self.tableViewFrame.reloadData()
print("ALL COMPLETE")
}
}
}
By using this, you will get an event to reload the tableviews after getting all the data.通过使用它,您将获得一个事件以在获取所有数据后重新加载 tableviews。
Let me know if this helps.让我知道这是否有帮助。
Enjoy and Share...享受和分享...
You can always return a default non styled UITableViewCell.你总是可以返回一个默认的非样式 UITableViewCell。 Also stop force unwrapping.
也停止强制展开。 You should always guard with if let or guard let.
您应该始终使用 if let 或 guard let 来保护。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
...
return UITableViewCell()
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.