Skip to content

Conversation

@JunSong-SH
Copy link
Contributor

[HCPSDKFIORIUIKIT-2964] SwiftUI Hierarchy View

This PR address the change for SwiftUI Hierarchy View migration according to the UIkit Hierarchy View. The main changes include the following items:

  1. Migrated most of functions except the async loading, iPad layout and copiable function.
  2. Added demo example for HierarchyView and HierarchyItemView.
  3. Added the Unit test for HierarchyView.

@JunSong-SH JunSong-SH requested a review from a team as a code owner October 28, 2025 10:57
@JunSong-SH JunSong-SH requested review from billzhou0223 and removed request for a team October 28, 2025 10:57
// Wrapper the header's trailing accessory clickable with default logic both the default and customized trailing accessory
init(_ configuration: TrailingAccessoryConfiguration, modelObject: HierarchyViewModelObject, isRTL: Bool) {
let trailingAccessory: () -> AnyView = {
if let _ = isRTL ? modelObject.parentID : modelObject.childID, let _ = modelObject.currentID {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused Optional Binding Violation: Prefer != nil over let _ = (unused_optional_binding)

// Wrapper the header's leading accessory clickable with default logic both the default and customized leading accessory
init(_ configuration: LeadingAccessoryConfiguration, modelObject: HierarchyViewModelObject, isRTL: Bool) {
let leadingAccessory: () -> AnyView = {
if let _ = isRTL ? modelObject.childID : modelObject.parentID, let _ = modelObject.currentID {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused Optional Binding Violation: Prefer != nil over let _ = (unused_optional_binding)

// Wrapper the header's trailing accessory clickable with default logic both the default and customized trailing accessory
init(_ configuration: TrailingAccessoryConfiguration, modelObject: HierarchyViewModelObject, isRTL: Bool) {
let trailingAccessory: () -> AnyView = {
if let _ = isRTL ? modelObject.parentID : modelObject.childID, let _ = modelObject.currentID {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused Optional Binding Violation: Prefer != nil over let _ = (unused_optional_binding)

// Wrapper the header's leading accessory clickable with default logic both the default and customized leading accessory
init(_ configuration: LeadingAccessoryConfiguration, modelObject: HierarchyViewModelObject, isRTL: Bool) {
let leadingAccessory: () -> AnyView = {
if let _ = isRTL ? modelObject.childID : modelObject.parentID, let _ = modelObject.currentID {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused Optional Binding Violation: Prefer != nil over let _ = (unused_optional_binding)

@cla-assistant
Copy link

cla-assistant bot commented Oct 28, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ dyongxu
✅ JunSong-SH
❌ I063052


I063052 seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@dyongxu
Copy link
Contributor

dyongxu commented Oct 28, 2025

@JunSong-SH I used copilot to generate the API doc, you can review it and see if to be adopted. It is for HierarchyView. I have not used it for other public APIs yet. You may see if copilot helps with your other public APIs as well

/// HierarchyView displays tree-structured data using HierarchyItemView rows and an optional HierarchyHeader.
///
/// ### Overview
/// Use HierarchyView when you need to browse, navigate, and (optionally) select items in a hierarchical data set (parent/child relationships).
/// The component delegates data access to a HierarchyViewDataSource so large or dynamic trees can be served efficiently.
///
/// ### Key Features
/// - Pluggable data source (HierarchyViewDataSource) defining root, children, parent lookups, and titles.
/// - Custom per-item view content via the hierarchyItem closure.
/// - Optional header view for navigation controls / summary information.
/// - Built-in selection handling (single / multiple / none) controlled by the hierarchyItemSelectionMode environment value.
/// - Style system (Fiori & custom) applied through hierarchyViewStyle modifiers.
///
/// ### Data Source Contract (Summary)
/// Your data source must provide stable, unique String identifiers for every item. Child counts and IDs should remain consistent during a single render pass.
/// See HierarchyViewDataSource for full protocol requirements.
///
/// ### State Bindings
/// - activeChildItem: The identifier that will become active (e.g. next navigated child) when the user invokes forward navigation in the header.
/// - selectedItems: Collection of currently selected item IDs (optional array in the generated initializer).
/// In single-selection mode only the first element is considered; in multiple-selection mode all elements are used.
///
/// ### Selection Mode
/// Controlled externally via environment:
/// swift /// .environment(\.hierarchyItemSelectionMode, .none) // selection disabled /// .environment(\.hierarchyItemSelectionMode, .single) // single selection /// .environment(\.hierarchyItemSelectionMode, .multiple) // multi selection ///
/// Selection affordances (selection buttons) are only visible while EditMode is .active and the selection mode is not .none.
///
/// ### Usage
/// #### 1. Simple Initialization (generated initializer)
/// swift /// @State private var activeChild: String? = nil /// @State private var selected: [String]? = [] /// let dataSource = HierarchySimpleDataSource() /// /// HierarchyView( /// dataSource: dataSource, /// hierarchyItem: { id in Text(id) }, /// activeChildItem: $activeChild, /// selectedItems: $selected /// ) /// .environment(\.editMode, .constant(.active)) /// .environment(\.hierarchyItemSelectionMode, .multiple) ///
///
/// #### 2. Single Selection Convenience (see public API extension)
/// swift /// @State private var activeChild: String? = nil /// @State private var selected: String? = nil /// HierarchyView.singleSelection( /// dataSource: dataSource, /// hierarchyItem: { id in Text(id) }, /// activeChildItem: $activeChild, /// selectedItem: $selected /// ) /// .environment(\.hierarchyItemSelectionMode, .single) ///
///
/// #### 3. Multi Selection With Set (see public API extension)
/// swift /// @State private var activeChild: String? = nil /// @State private var selectedSet: Set<String> = [] /// HierarchyView.multiSelection( /// dataSource: dataSource, /// hierarchyItem: { id in Text(id) }, /// activeChildItem: $activeChild, /// selectedItems: $selectedSet /// ) /// .environment(\.hierarchyItemSelectionMode, .multiple) ///
///
/// ### Styling
/// Apply or compose styles using:
/// swift /// HierarchyView(...) /// .hierarchyViewStyle(MyCustomHierarchyStyle()) ///
/// Custom styles implement HierarchyViewStyle and can be layered; the environment maintains an internal style stack.
///
/// ### Performance Tips
/// - Provide O(1) or cached implementations for child count / child ID queries.
/// - Use stable IDs; avoid generating random IDs each render.
/// - Prefer lazy expansion of deep branches (render children only when needed) in your item view logic.
///
/// ### See Also
/// HierarchyViewDataSource, HierarchyItemView, HierarchyHeader, HierarchyIndicator, HierarchyViewStyle.

Copy link
Contributor

@dyongxu dyongxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Enhance API doc and unit test, Try to hit 80% code coverage
  2. The fiori icons come from Design Team. So, usually we don't add our new icons, since they follow some design pattern.
    This PR adds some new icons , does it mean UIKit SDK uses different icons ?

@dyongxu
Copy link
Contributor

dyongxu commented Oct 28, 2025

@JunSong-SH See if these files (with low or 0%)c an be covered in unit tests. You can try to use copilot to write unit tests

Screenshot 2025-10-28 at 12 29 48 PM

@JunSong-SH
Copy link
Contributor Author

  1. Enhance API doc and unit test, Try to hit 80% code coverage
  2. The fiori icons come from Design Team. So, usually we don't add our new icons, since they follow some design pattern.
    This PR adds some new icons , does it mean UIKit SDK uses different icons ?

The two newly added icons come from the FioriIcon library used in the UIKit HierarchyIndicator. See below links:
https://github.tools.sap/Tango/SAPFiori/tree/main/src/Frameworks/SAPFiori/SAPFiori/OfficialFioriIcons.xcassets/iOS%20SDK%20Library/SFSymbols/hierarchy.off.symbolset
https://github.tools.sap/Tango/SAPFiori/tree/main/src/Frameworks/SAPFiori/SAPFiori/OfficialFioriIcons.xcassets/iOS%20SDK%20Library/SFSymbols/hierarchy.on.symbolset
However, they are not present in the FioriIcon library used for SwiftUI Fiori controls, so I assume they should be added.

@JunSong-SH
Copy link
Contributor Author

Screenshot 2025-11-03 at 16 04 47 Enhance unit test and API doc.

@JunSong-SH JunSong-SH requested a review from dyongxu November 3, 2025 10:46
import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)

import Foundation
import SwiftUI

/**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned Doc Comment Violation: A doc comment should be attached to a declaration. (orphaned_doc_comment)


public static let hierarchyOff = Image(fioriName: "fiori.hierarchy.off")

public static let hierarchyOn = Image(fioriName: "fiori.hierarchy.on")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Docs Violation: public declarations should be documented. (missing_docs)

///
public static let zoomOut = Image(fioriName: "fiori.zoom.out")

public static let hierarchyOff = Image(fioriName: "fiori.hierarchy.off")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Docs Violation: public declarations should be documented. (missing_docs)

@zzchao-1999 zzchao-1999 merged commit f12b762 into SAP:main Nov 4, 2025
15 of 20 checks passed
@JunSong-SH JunSong-SH deleted the main-hview branch November 4, 2025 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants