En el paso anterior, registró las celdas requeridas, configuró la Vista de tabla y aplicó los stumps del método para la fuente de datos y la delegación. Implementará estos métodos paso a paso para que la tabla muestre datos y responda a la interacción del usuario.
Se supone que la vista de tabla consta de dos partes, una para los clientes y otra para los productos.
Regreso 2 sa
numberOfSections(in:)
:
override func numberOfSections(in tableView: UITableView) -> Int { return 2 }Cada sección debe ser única, para ello puede indicarle a la tabla Ver qué encabezados de vista de tabla deben mostrar.
Pon el
tableView(_:viewForHeaderInSection:)
como sigue:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { // First dequeue the Header Footer View you registered in the viewDidLoad(:). let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: FUITableViewHeaderFooterView.reuseIdentifier) as! FUITableViewHeaderFooterView // Set it's style to title. header.style = .title header.separators = .bottom // For the first section give back a Header that is for the customers and the second is for the products switch section { case 0: header.titleLabel.text = "Customers" break case 1: header.titleLabel.text = "Products" break default: break } return header }El pie de página de las secciones se utilizará como separadores. Estos divisores no tienen un significado funcional, pero hacen que la interfaz de usuario sea más limpia.
Pon el
tableView(_:viewForFooterInSection:)
como sigue:
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { if section == 1 { return UIView() } let divider = UITableViewHeaderFooterView() divider.backgroundColor = .preferredFioriColor(forStyle: .backgroundBase) return divider }Ahora encontrando los métodos de fuente de datos reales requeridos. El es
tableView(_:numberOfRowsInSection:)
es bastante simple de implementar:
// If the data arrays are empty return 0, else return 5. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 0: if customers.isEmpty { return 0 } case 1: if products.isEmpty { return 0 } default: return 0 } return 5 }Llegando a la parte emocionante, tomando el
tableView(_:cellForRowAt:)
método. Este método se denomina vista de tabla cada vez que necesita desinflar una celda.Aplique el siguiente código y lea los comentarios en línea con atención:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Dequeue the FUIObjectTableViewCell and cast it accordingly. let cell = tableView.dequeueReusableCell(withIdentifier: FUIObjectTableViewCell.reuseIdentifier) as! FUIObjectTableViewCell // Set the accessory type of the cell to disclosure, this will indicate to the user that those cells are tappable. cell.accessoryType = .disclosureIndicator // Distinct the cell setup depending on the section. switch indexPath.section { case 0: // Get the currently needed customer and fill the cell's properties let customer = customers[indexPath.row] cell.headlineText = "(customer.firstName ?? "") (customer.lastName ?? "")" cell.subheadlineText = "(customer.city ?? ""), (customer.country ?? "")" cell.footnoteText = "# Sales Orders : (customer.salesOrders.count)" return cell case 1: // Get the currently needed product and fill the cell's properties let product = products[indexPath.row] cell.headlineText = product.name ?? "" cell.subheadlineText = product.categoryName ?? "" // If there is a product price set, format it with the help of a NumberFormatter if let price = product.price { let formatter = NumberFormatter() formatter.numberStyle = .currency let formattedPrice = formatter.string(for: price.intValue()) cell.footnoteText = formattedPrice ?? "" } return cell default: return UITableViewCell() } }Su controlador de vista de tabla ahora debería verse así:
//
// OverviewTableViewController.swift
// SalesAssistant
//
// Created by Muessig, Kevin on 03.11.20.
// Copyright © 2020 SAP. All rights reserved.
//
import UIKit
import SAPFiori
import SAPOData
import SAPOfflineOData
import SAPCommon
import SAPFoundation
import SAPFioriFlows
class OverviewTableViewController: UITableViewController, SAPFioriLoadingIndicator {
// The Logger is already setup in the AppDelegate through the SAP iOS Assistant, that's why you can easily can get an instance here.
private let logger = Logger.shared(named: "OverviewViewController")
var loadingIndicator: FUILoadingIndicatorView?
private var customers = [Customer]()
private var products = [Product]()
/// First retrieve the destinations your app can talk to from the AppParameters.
let destinations = FileConfigurationProvider("AppParameters").provideConfiguration().configuration["Destinations"] as! NSDictionary
var dataService: ESPMContainer<OfflineODataProvider>? {
guard let odataController = OnboardingSessionManager.shared.onboardingSession?.odataControllers[destinations["com.sap.edm.sampleservice.v2"] as! String] as? Comsapedmsampleservicev2OfflineODataController, let dataService = odataController.espmContainer else {
AlertHelper.displayAlert(with: NSLocalizedString("OData service is not reachable, please onboard again.", comment: ""), error: nil, viewController: self)
return nil
}
return dataService
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .preferredFioriColor(forStyle: .backgroundBase)
// Define the estimated row height for each row as well as setting the actual row height to define it's dimension itself.
// This will cause the Table View to display a cell for at least 80 points.
tableView.estimatedRowHeight = 80
tableView.rowHeight = UITableView.automaticDimension
// Register an FUIObjectTableViewCell and a FUITableViewHeaderFooterView. You can use the convenience reuse identifier defined in the cell classes to later dequeue the cells.
tableView.register(FUIObjectTableViewCell.self, forCellReuseIdentifier: FUIObjectTableViewCell.reuseIdentifier)
tableView.register(FUITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: FUITableViewHeaderFooterView.reuseIdentifier)
loadInitialData()
}
private func loadInitialData() {
// start showing the loading indicator
self.showFioriLoadingIndicator()
// Using a DispatchGroup will help you to get notified when all the needed data sets are loaded
let group = DispatchGroup()
// Fetch customers and products, pass in the DispatchGroup to handle entering and leaving of the group
fetchCustomers(group)
fetchProducts(group)
// When all data tasks are completed, hide the loading indicator and reload the table view. This will cause a refresh of the UI, displaying the newly loaded data
group.notify(queue: DispatchQueue.main) {
self.hideFioriLoadingIndicator()
self.tableView.reloadData()
}
}
private func fetchCustomers(_ group: DispatchGroup) {
// Enter the DispatchGroup
group.enter()
// Define a Data Query which is a class of the SAPOData framework. This query will tell the OData Service to also load the available Sales Orders for each Customer
let query = DataQuery().expand(Customer.salesOrders)
// Now call the data service and fetch the customers matching the above defined query. When during runtime the block gets entered you expect a result or an error. Also you want to hold a weak reference of self to not run into object reference issues during runtime.
dataService?.fetchCustomers(matching: query) { [weak self] result, error in
// If there is an error show an AlertDialog using the generated convenience class AlertHelper. Also log the error to the console and leave the /group.
if let error = error {
AlertHelper.displayAlert(with: "Failed to load list of customers!", error: error, viewController: self!)
self?.logger.error("Failed to load list of customers!", error: error)
group.leave()
return
}
// sort the customer result set by the number of available sales orders by customer.
self?.customers = result!.sorted(by: { $0.salesOrders.count > $1.salesOrders.count })
group.leave()
}
}
private func fetchProducts(_ group: DispatchGroup) {
// Enter the DispatchGroup
group.enter()
// Define a Data Query only fetching the top 5 products.
let query = DataQuery().top(5)
dataService?.fetchProducts(matching: query) { [weak self] result, error in
if let error = error {
AlertHelper.displayAlert(with: "Failed to load list of products!", error: error, viewController: self!)
self?.logger.error("Failed to load list of products!", error: error)
group.leave()
return
}
self?.products = result!
group.leave()
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// First dequeue the Header Footer View you registered in the viewDidLoad(:).
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: FUITableViewHeaderFooterView.reuseIdentifier) as! FUITableViewHeaderFooterView
// Set it's style to title.
header.style = .title
header.separators = .bottom
// For the first section give back a Header that is for the customers and the second is for the products
switch section {
case 0:
header.titleLabel.text = "Customers"
break
case 1:
header.titleLabel.text = "Products"
break
default:
break
}
return header
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if section == 1 { return UIView() }
let divider = UITableViewHeaderFooterView()
divider.backgroundColor = .preferredFioriColor(forStyle: .backgroundBase)
return divider
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
if customers.isEmpty { return 0 }
case 1:
if products.isEmpty { return 0 }
default:
return 0
}
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue the FUIObjectTableViewCell and cast it accordingly.
let cell = tableView.dequeueReusableCell(withIdentifier: FUIObjectTableViewCell.reuseIdentifier) as! FUIObjectTableViewCell
// Set the accessory type of the cell to disclosure, this will indicate to the user that those cells are tappable.
cell.accessoryType = .disclosureIndicator
// Distinct the cell setup depending on the section.
switch indexPath.section {
case 0:
// Get the currently needed customer and fill the cell's properties
let customer = customers[indexPath.row]
cell.headlineText = "(customer.firstName ?? "") (customer.lastName ?? "")"
cell.subheadlineText = "(customer.city ?? ""), (customer.country ?? "")"
cell.footnoteText = "# Sales Orders : (customer.salesOrders.count)"
return cell
case 1:
// Get the currently needed product and fill the cell's properties
let product = products[indexPath.row]
cell.headlineText = product.name ?? ""
cell.subheadlineText = product.categoryName ?? ""
// If there is a product price set, format it with the help of a NumberFormatter
if let price = product.price {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
let formattedPrice = formatter.string(for: price.intValue())
cell.footnoteText = formattedPrice ?? ""
}
return cell
default:
return UITableViewCell()
}
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//TODO: Implement
}
}