Skip to content

Commit 619ecb0

Browse files
authored
Merge pull request #18281 from MatthiasdelaRoche/master
Automatic commit: Move 'fiori-ios-scpms-floorplan' from QA to Production
2 parents c8e75fa + 1a424df commit 619ecb0

1 file changed

Lines changed: 64 additions & 36 deletions

File tree

tutorials/fiori-ios-scpms-floorplan/fiori-ios-scpms-floorplan.md

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
title: Create a List Report Floorplan
33
description: Use the SAP BTP SDK for iOS to build a simple List Report Floorplan containing an FUISearchBar
44
auto_validation: true
5-
primary_tag: products>ios-sdk-for-sap-btp
6-
tags: [ tutorial>intermediate, operating-system>ios, topic>mobile, topic>odata, products>sap-business-technology-platform, products>sap-mobile-services ]
5+
primary_tag: software-product>sap-btp-sdk-for-ios
6+
tags: [ tutorial>intermediate, operating-system>ios, topic>mobile, programming-tool>odata, software-product>sap-business-technology-platform, software-product>sap-mobile-services ]
7+
78
time: 60
89
---
910

@@ -84,6 +85,10 @@ with:
8485

8586
```
8687

88+
89+
90+
91+
8792
Instead of instantiating the `MainSplitViewController` the `UIStoryboard` will instantiate the initial View Controller and cast it to the `UINavigationController`.
8893

8994
Lastly, you have to create a new class inheriting from `UITableViewController`. Create a new **Cocoa Touch Class** and name it `SupplierTableViewController`.
@@ -104,11 +109,15 @@ Now that you have the first `UITableViewController` setup you will add code to l
104109
Open the `SupplierTableViewController.swift` class and add the following import statements for full usage of the SDK:
105110

106111
```Swift
112+
import UIKit
107113
import SAPOData
108114
import SAPFoundation
109115
import SAPFiori
110116
import SAPFioriFlows
111117
import SAPCommon
118+
import SAPOfflineOData
119+
import ESPMContainerFmwk
120+
import SharedFmwk
112121

113122
```
114123

@@ -133,19 +142,22 @@ Implement the following lines of code directly below the loading indicator prope
133142
let destinations = FileConfigurationProvider("AppParameters").provideConfiguration().configuration["Destinations"] as! NSDictionary
134143

135144
// Retrieve the data service using the destinations dictionary and return it.
136-
var dataService: ESPMContainer<OnlineODataProvider>? {
137-
guard let odataController = OnboardingSessionManager.shared.onboardingSession?.odataControllers[destinations["com.sap.edm.sampleservice.v2"] as! String] as? Comsapedmsampleservicev2OnlineODataController, let dataService = odataController.espmContainer else {
138-
AlertHelper.displayAlert(with: NSLocalizedString("OData service is not reachable, please onboard again.", comment: ""), error: nil, viewController: self)
145+
let destinations = FileConfigurationProvider("AppParameters").provideConfiguration().configuration["Destinations"] as! NSDictionary
146+
147+
var dataService: ESPMContainer<OfflineODataProvider>? {
148+
guard let odataController = OnboardingSessionManager.shared.onboardingSession?.odataControllers[ODataContainerType.eSPMContainer.description] as? ESPMContainerOfflineODataController, let dataService = odataController.dataService else {
149+
AlertHelper.displayAlert(with: "OData service is not reachable, please onboard again.", error: nil, viewController: self)
139150
return nil
140151
}
141152
return dataService
142153
}
143154

144155
```
145156

146-
> In case you're using an `ODataOfflineProvider` you have to change the above-mentioned code to use `ODataOfflineProvider` instead of `ODataOnlineProvider`. You have to also import `SAPOfflineOData` in addition to the `SAPOData` framework.
147157

148-
Because we're good citizens we want to use an app logger to log important information. Fortunately, SAP is offering a simple-to-use Logging API with the `SAPCommon` framework.
158+
> In case you're using an `OfflineODataProvider` you have to change the above-mentioned code to use `OfflineODataProvider` instead of `OnlineODataProvider`. You have to also import `SAPOfflineOData` in addition to the `SAPOData` framework.
159+
160+
SAP offers a simple-to-use Logging API with the `SAPCommon` framework.
149161

150162
Implement the following line of code below the data service declaration:
151163

@@ -163,7 +175,7 @@ private var suppliers = [Supplier]()
163175

164176
```
165177

166-
> From now on bigger code blocks are explained with inline comments. Read the inline comments carefully to fully understand what the code is doing and why we're implementing it.
178+
> From now on bigger code blocks are explained with inline comments. Read the inline comments carefully to fully understand what the code is doing and why you're implementing it.
167179
168180
Loading all available suppliers is fairly easy using the generated data service. The generated code will handle all authentication and authorization challenges for you and the data service will construct all necessary requests to load, create and update entities in your backend.
169181

@@ -224,7 +236,7 @@ First, implement two necessary `UITableViewDataSource` methods which are respons
224236
```Swift
225237
// MARK: - Table view data source
226238

227-
/// We are only displaying one section for this screen, return 1.
239+
/// Only one section is displayed for this screen, return 1.
228240
override func numberOfSections(in tableView: UITableView) -> Int {
229241
return 1
230242
}
@@ -237,17 +249,17 @@ override func tableView(_ tableView: UITableView, numberOfRowsInSection section:
237249
```
238250

239251
Using the `SAPFiori` framework allows you to choose from a large variety of `UITableViewCell` classes.
240-
Because we're going to display suppliers, and those have a name, an address and probably contact data the `FUIContactCell` would be a perfect fit here. You can always use the **SAP Fiori Mentor** app, available in the App Store for iPad, to get an introduction to the control.
252+
Because you're going to display suppliers, and those have a name, an address and probably contact data the `FUIContactCell` would be a perfect fit here. You can always use the **SAP Fiori Mentor** app, available in the App Store for iPad, to get an introduction to the control.
241253

242-
Before implementing the `tableView(_cellForRowAt:)` method responsible for dequeuing reusable cells and returning them to the `UITableView`, we want to register the `FUIContactCell` with the `UITableView` first. This is usually done in the `viewDidLoad()` method.
254+
Before implementing the `tableView(_cellForRowAt:)` method responsible for dequeuing reusable cells and returning them to the `UITableView`, You need to register the `FUIContactCell` with the `UITableView` first. This is usually done in the `viewDidLoad()` method.
243255

244256
Implement the following lines of code before the `updateTableView()` method call in the `viewDidLoad()`:
245257

246258
```Swift
247259
// Register the cell with the provided convenience reuse identifier.
248260
tableView.register(FUIContactCell.self, forCellReuseIdentifier: FUIContactCell.reuseIdentifier)
249261

250-
// Set the seperator style of the table view to none and the background color to the standard Fiori background base color.
262+
// Set the separator style of the table view to none and the background colour to the standard Fiori background base colour.
251263
tableView.separatorStyle = .none
252264
tableView.backgroundColor = .preferredFioriColor(forStyle: .backgroundBase)
253265

@@ -268,7 +280,7 @@ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexP
268280
cell.headlineText = supplier.supplierName ?? "No Name available!"
269281
cell.subheadlineText = "\(supplier.street ?? "") \(supplier.houseNumber ?? "") \(supplier.city ?? ""), \(supplier.postalCode ?? "") \(supplier.country ?? "")"
270282

271-
// Because we're going to implement navigation later this cell has the disclosure indicator as accessory type indicating that navigation to the user.
283+
//Navigation will be implemented later. This cell has the disclosure indicator as accessory type indicating that navigation to the user.
272284
cell.accessoryType = .disclosureIndicator
273285

274286
return cell
@@ -303,7 +315,7 @@ Go back to the `tableView(_cellForRowAt:)` method and add the following lines of
303315
cell.activityControl.addActivities(activities)
304316
cell.activityControl.maxVisibleItems = 3
305317

306-
// The FUIActivityControl provides you two different ways of reacting to user's interaction. One would be with a change handler the other would be with a delegate. Because I don't want the communication logic being in the tableView(_cellForRowAt:) method we're using the delegation way.
318+
// The FUIActivityControl provides you two different ways of reacting to user's interaction. One would be with a change handler the other would be with a delegate. Because I don't want the communication logic being in the tableView(_cellForRowAt:) method you're using the delegation way.
307319
cell.activityControl.delegate = self
308320

309321
```
@@ -354,8 +366,8 @@ Tapping on one of the `FUIActivityItem` will result in an alert dialogue showing
354366

355367
[ACCORDION-BEGIN [Step 5: ](Implement the Navigation Between the Supplier List and the Product List)]
356368

357-
In this step, we will implement a second `UITableViewController` displaying all products a supplier provides.
358-
For this, we will use a storyboard segue to navigate to the `SupplierProductsTableViewController` and pass through the selected supplier.
369+
In this step, you will implement a second `UITableViewController` displaying all products a supplier provides.
370+
For this, you will use a storyboard segue to navigate to the `SupplierProductsTableViewController` and pass through the selected supplier.
359371

360372
> In case you're not familiar with segues please visit, and carefully read the official documentation before continuing. [Using Segues](https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html)
361373
@@ -389,9 +401,9 @@ var supplier: Supplier!
389401

390402
```
391403

392-
Next, we will implement the `prepare(:for:sender:)` method which is responsible for making necessary preparations before the navigation is fully executed. In our case, we will pass the selected supplier to the `SupplierProductsTableViewController`.
404+
Next, you will implement the `prepare(:for:sender:)` method which is responsible for making necessary preparations before the navigation is fully executed. In our case, you will pass the selected supplier to the `SupplierProductsTableViewController`.
393405

394-
Implement the `prepare(:for:sender:)` method:
406+
Implement the `prepare(:for:sender:)` method in `SupplierTableViewController`:
395407

396408
```Swift
397409
// MARK: - Navigation
@@ -415,7 +427,7 @@ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
415427

416428
```
417429

418-
You can utilize the `tableView(_:didSelectRowAt:)` method to trigger the navigation. Implement the override method:
430+
You can utilise the `tableView(_:didSelectRowAt:)` method to trigger the navigation. Implement the override method:
419431

420432
```Swift
421433
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@@ -431,16 +443,20 @@ You can now navigate back and forth between the `SupplierTableViewController` an
431443

432444
[ACCORDION-BEGIN [Step 6: ](Implement the loading and displaying of supplier-specific products)]
433445

434-
This view is similar to the `SupplierTableViewController` but instead of fetching all products, we will fetch supplier-specific products. To achieve that, we again can utilize the OData APIs. `SAPOData` provides the possibility to create so-called `DataQuery` objects which can define typical OData arguments for a backend call.
446+
This view is similar to the `SupplierTableViewController` but instead of fetching all products, you will fetch supplier-specific products. To achieve that, you again can utilise the OData APIs. `SAPOData` provides the possibility to create so-called `DataQuery` objects which can define typical OData arguments for a backend call.
435447

436-
First, we need to make the needed import statements for that class:
448+
First, you need to make the needed import statements for that class:
437449

438450
```Swift
439451
import SAPOData
440452
import SAPFoundation
441453
import SAPFiori
442454
import SAPFioriFlows
443455
import SAPCommon
456+
import ESPMContainerFmwk
457+
import SharedFmwk
458+
import SAPOfflineOData
459+
444460

445461
```
446462

@@ -457,10 +473,9 @@ Add a couple of class properties necessary for the data service instance and the
457473
// The available destinations from Mobile Services are hold in the FileConfigurationProvider. Retrieve it to find the correct data service
458474
let destinations = FileConfigurationProvider("AppParameters").provideConfiguration().configuration["Destinations"] as! NSDictionary
459475

460-
// Retrieve the data service using the destinations dictionary and return it.
461-
var dataService: ESPMContainer<OnlineODataProvider>? {
462-
guard let odataController = OnboardingSessionManager.shared.onboardingSession?.odataControllers[destinations["com.sap.edm.sampleservice.v2"] as! String] as? Comsapedmsampleservicev2OnlineODataController, let dataService = odataController.espmContainer else {
463-
AlertHelper.displayAlert(with: NSLocalizedString("OData service is not reachable, please onboard again.", comment: ""), error: nil, viewController: self)
476+
var dataService: ESPMContainer<OfflineODataProvider>? {
477+
guard let odataController = OnboardingSessionManager.shared.onboardingSession?.odataControllers[ODataContainerType.eSPMContainer.description] as? ESPMContainerOfflineODataController, let dataService = odataController.dataService else {
478+
AlertHelper.displayAlert(with: "OData service is not reachable, please onboard again.", error: nil, viewController: self)
464479
return nil
465480
}
466481
return dataService
@@ -470,7 +485,7 @@ private var products = [Product]()
470485

471486
```
472487

473-
Also, we want to utilize the provided loading indicator. Let the class conform to the `SAPFioriLoadingIndicator` protocol.
488+
Also, you want to utilise the provided loading indicator. Let the class conform to the `SAPFioriLoadingIndicator` protocol.
474489

475490
```Swift
476491
class SupplierProductsTableViewController: UITableViewController, SAPFioriLoadingIndicator { ... }
@@ -486,7 +501,7 @@ var loadingIndicator: FUILoadingIndicatorView?
486501

487502
Let's load some data!
488503

489-
We're using the same style we've used in the `SupplierTableViewController`. Implement the following two methods and read the inline comments carefully because you will see that we utilize the `DataQuery` object for making a filter as well as an expand.
504+
You're using the same style you've used in the `SupplierTableViewController`. Implement the following two methods and read the inline comments carefully because you will see that you utilise the `DataQuery` object for making a filter as well as an expand.
490505

491506
> If you're not familiar with those OData specific terms please make yourself familiar with the OData specification:
492507
@@ -539,7 +554,7 @@ override func viewDidLoad() {
539554

540555
```
541556

542-
We know that the products contain images for each product, it would be nice to display them as well, but to do so we have to write a little bit of code to make that happen.
557+
Products contain images for each product, it would be nice to display them as well, but to do so you have to write a little bit of code to make that happen.
543558

544559
First, implement a class property holding the image URLs of all products.
545560

@@ -548,7 +563,7 @@ private var productImageURLs = [String]()
548563

549564
```
550565

551-
The user might want to scroll through the products even if the images are not fully loaded yet we have to implement a simple image cache as well as a placeholder image to keep the performance of the table stable.
566+
The user might want to scroll through the products even if the images are not fully loaded yet you have to implement a simple image cache as well as a placeholder image to keep the performance of the table stable.
552567

553568
Add the following line of code directly below the `productImageURLs`:
554569

@@ -591,7 +606,7 @@ private func loadProductImageFrom(_ url: URL, completionHandler: @escaping (_ im
591606

592607
```
593608

594-
Now we have the foundation for fetching and caching images. You were probably wondering where the mapping from the fetched products to the product image URLs happens. We will implement that now.
609+
Now you have the foundation for fetching and caching images. You were probably wondering where the mapping from the fetched products to the product image URLs happens. You will implement that now.
595610

596611
Go back to the `loadData(:)` method and add the following line of code directly below the product assignment. Your `loadData(:)` should look like this now:
597612

@@ -619,7 +634,7 @@ private func loadData(completionHandler: @escaping () -> Void) {
619634

620635
```
621636

622-
Like the last time we have to register a `SAPFiori` cell with the `UITableView`, but this time it is a `FUIObjectTableViewCell`. Add the following lines of code to the `viewDidLoad()` method right before the `updateTableView(:)` method call.
637+
Like the last time you have to register a `SAPFiori` cell with the `UITableView`, but this time it is a `FUIObjectTableViewCell`. Add the following lines of code to the `viewDidLoad()` method right before the `updateTableView(:)` method call.
623638

624639
```Swift
625640
tableView.register(FUIObjectTableViewCell.self, forCellReuseIdentifier: FUIObjectTableViewCell.reuseIdentifier)
@@ -628,9 +643,9 @@ tableView.backgroundColor = .preferredFioriColor(forStyle: .backgroundBase)
628643

629644
```
630645

631-
As the last step, we have to implement the table views data source methods similar to the `SupplierTableViewController`.
646+
As the last step, you have to implement the table views data source methods similar to the `SupplierTableViewController`.
632647

633-
Add the following methods directly below the `loadProductImageFrom(_:completionHandler:)` method:
648+
Add the following methods directly below the `loadProductImageFrom(_:completionHandler:)` method and make sure to modify the value of `baseURL`
634649

635650
```Swift
636651
// MARK: - Table view data source
@@ -729,7 +744,7 @@ private func setupSearchBar() {
729744

730745
Xcode will complain now because the `SupplierProductsTableViewController.swift` class is not conforming to the [`UISearchResultsUpdating`](https://developer.apple.com/documentation/uikit/uisearchresultsupdating) protocol.
731746

732-
Add an extension to your class, like we did in the `SupplierTableViewController`:
747+
Add an extension to your class, like you did in the `SupplierTableViewController`:
733748

734749
```Swift
735750
extension SupplierProductsTableViewController: UISearchResultsUpdating {
@@ -740,11 +755,24 @@ extension SupplierProductsTableViewController: UISearchResultsUpdating {
740755

741756
```
742757

758+
759+
Call the `setupSearchBar()` method inside the `viewDidLoad()`:
760+
761+
```Swift
762+
override func viewDidLoad() {
763+
super.viewDidLoad()
764+
setupSearchBar()
765+
updateTableView()
766+
767+
}
768+
769+
```
770+
743771
If you run the app now you should see the `FUISearchBar` being displayed above the `UITableView`.
744772

745773
![Supplier Product List](fiori-ios-scpms-floorplan-14.png)
746774

747-
Now we have to implement some search logic to be called in the `updateSearchResults(for:)` method.
775+
Now you have to implement some search logic to be called in the `updateSearchResults(for:)` method.
748776

749777
Implement the following methods right below the `setupSearchBar()` method and carefully read the inline comments.
750778

@@ -775,7 +803,7 @@ Cool! Let's implement the `updateSearchResults(for:)` method:
775803
```Swift
776804
extension SupplierProductsTableViewController: UISearchResultsUpdating {
777805
func updateSearchResults(for searchController: UISearchController) {
778-
// Get the searched-for term, note here that we don't have a time bouncer which waits for the user to finish its input. You could implement that if needed, for this simple example we do life searches for each character. I wouldn't recommend doing that over a large data set.
806+
// Get the searched-for term, note here that you don't have a time bouncer which waits for the user to finish its input. You could implement that if needed, for this simple example you do life searches for each character. I wouldn't recommend doing that over a large data set.
779807
if let searchText = searchController.searchBar.text {
780808
// Feed it to the search logic.
781809
searchProducts(searchText)

0 commit comments

Comments
 (0)