集合视图树表示

问题描述 投票:0回答:3

我正在考虑在

iOS
中创建树数据结构的某种可视化表示。树中节点保存的数据是图像和标签,一个节点最多可以有 6 个子节点。

目前,我有一个带有自定义布局的集合视图,当我遍历自制树时,我以编程方式计算每个节点的 x 和 y。

这个解决方案有效,但只是勉强有效。当我构建更多功能时,我预计它会崩溃。

我考虑过在树构建完成后制作图像并仅使用图像视图,但我计划在分支上实现某种展开/折叠。我还需要一种放大和缩小整个树的方法,这对于集合视图来说似乎并不容易。

有更好的解决方案吗?

ios objective-c uicollectionview
3个回答
4
投票

在 UIScrollView 中只使用简单的视图怎么样?

这样你就可以:

  • 控制每个节点的展开和折叠。
  • 放大和缩小以获得整体或细节视图。
  • 在巨大的树结构的情况下滚动。

这里我使用

UIView
创建了一个示例项目: https://github.com/crisisGriega/swift-simple-tree-drawer

这是一个快速的开发,所以有很多东西可以改进,比如节点之间的线(连接器)的绘制方式。此外,在此示例中,节点被添加到

UIView
而不是
UIScrollView
。但您可以点击节点来显示/隐藏其子节点。


1
投票

考虑使用

SpriteKit
而不是使用
UIKit
组件。使用
UICollectionView
创建动态树状布局并不容易,因为您可以查看它的数据源定义,它不是为了建模树数据,而是为平面列表数据建模。如果数据模型根本不同,一切就变得很难进行。

使用

SpriteKit
,每个节点对象都可以由
SKSpriteNode
对象渲染。子节点的布局由其父节点管理。您还可以使用物理引擎自动定位节点,它还有一个额外的好处,即可以用最小的 efferot 避免重叠。最后但并非最不重要的一点是,
SpriteKit
开箱即用地支持缩放和滚动。


0
投票

假设一个示例模型:

import Foundation

struct TreeNode: Hashable, Sendable {
    let id = UUID()
    let name: String
    var children: [TreeNode]?

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    static func == (lhs: TreeNode, rhs: TreeNode) -> Bool {
        return lhs.id == rhs.id
    }
}

enum TreeSection: CaseIterable {
    case main
}

从 iOS 13.0 开始,可以轻松完成

import UIKit

final class TreeListController: UIViewController {
    typealias Item = TreeNode
    typealias Section = TreeSection
    typealias DS = UICollectionViewDiffableDataSource<Section, Item>
    typealias DSSnapshot = NSDiffableDataSourceSnapshot<Section, Item>
    typealias CellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item>

    private let items: [Item]
    private let collectionView: UICollectionView
    private let dataSource: DS

    init(treeNodes: [Item]) {
        let collectionView = Self.createCollectionView()
        let dataSource = Self.createDataSource(with: collectionView)
        self.items = treeNodes
        self.collectionView = collectionView
        self.dataSource = dataSource
        collectionView.dataSource = dataSource
        super.init(nibName: nil, bundle: nil)
    }

    @available(iOS, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        view = collectionView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        applySnapshot(treeNodes: items, to: .main)
    }

    private static func createCollectionView() -> UICollectionView {
        let config = UICollectionLayoutListConfiguration(appearance: .plain)
        let layout = UICollectionViewCompositionalLayout.list(using: config)
        return UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
    }

    private static func createDataSource(with collectionView: UICollectionView) -> DS {
        let cellRegistration: CellRegistration = {
            CellRegistration { cell, indexPath, treeNode in
                var config = cell.defaultContentConfiguration()
                config.image = UIImage(systemName: "folder")
                config.imageProperties.tintColor = .systemBlue
                config.text = treeNode.name
                cell.contentConfiguration = config
                // include disclosure indicator for nodes with children
                cell.accessories = treeNode.children != nil ? [.outlineDisclosure()] : []
            }
        }()

        return DS(collectionView: collectionView) { collectionView, indexPath, treeNode -> UICollectionViewCell? in
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: treeNode)
        }
    }

    private func applySnapshot(treeNodes: [TreeNode], to section: Section) {
        // reset section
        var snapshot = DSSnapshot()
        snapshot.appendSections([section])
        dataSource.apply(snapshot, animatingDifferences: false)

        // initial snapshot with the root nodes
        var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<TreeNode>()
        sectionSnapshot.append(treeNodes)

        func addItemsRecursively(_ nodes: [TreeNode], to parent: TreeNode?) {
            nodes.forEach { node in
                // for each node we add its children, then recurse into the children nodes
                if let children = node.children, !children.isEmpty {
                    sectionSnapshot.append(children, to: node)
                    addItemsRecursively(children, to: node)
                }

                // show every node expanded
                sectionSnapshot.expand([node])
            }
        }

        addItemsRecursively(treeNodes, to: nil)
        dataSource.apply(sectionSnapshot, to: section, animatingDifferences: true)
    }
}

示例内容:

let sampleTreeNodes = [TreeNode(name: "Folder 1", children: [
    TreeNode(name: "Subfolder 1-1"),
    TreeNode(name: "Subfolder 1-2", children: [
        TreeNode(name: "File 1"),
        TreeNode(name: "File 2")
    ])
])]

SwiftUI 的额外包装:

import SwiftUI
import SwiftData

struct TreeListRepresentableView: UIViewControllerRepresentable {
    typealias UIViewControllerType = TreeListController
    let treeNodes: [TreeNode]

    func makeUIViewController(context: Context) -> TreeListController {
        TreeListController(treeNodes: createSampleFolderItems())
    }

    func updateUIViewController(_ uiViewController: TreeListController, context: Context) {
    }

    func createSampleFolderItems() -> [TreeNode] {
        treeNodes
    }
}

#Preview {
    TreeListRepresentableView(treeNodes: sampleTreeNodes)
}

尝试了大约 50 个文件夹,看起来不错:

© www.soinside.com 2019 - 2024. All rights reserved.