Не удалось раскрыть раскрывающиеся элементы в представлении схемы Big Sur

Я начал новый проект macOS (в настоящее время находится на Big Sur beta 3), и NSOutlineViewузлы кажутся сломанными. Не могу сказать, это я или ОС.

Вот образец проекта, демонстрирующий проблему. И изображение...

Как видите, ячейка перекрывает шевроны расширения. Нажатие на любой из шевронов восстанавливает правильный макет первой строки, но не второй. Также методы автосохраненияpersistentObjectForItem а также itemForPersistentObject никогда не называются.

Тестовый проект очень прост - все, что я сделал, это добавил SourceViewкомпонент из библиотеки представления в проект приложения по умолчанию и подключите делегат / источник данных к контроллеру представления. Также проверилAutosave Expanded Items в IB и укажите имя в Autosaveполе. Вот весь код контроллера:

class ViewController: NSViewController {
    @IBOutlet var outlineView: NSOutlineView?

    let data = [Node("First item", 1), Node("Second item", 2)]
}

extension ViewController: NSOutlineViewDataSource {
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        data[index]
    }
    
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        true
    }
    
    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        item == nil ? data.count : 0
    }
    
    func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
        item
    }
    
    func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
        (item as? Node)?.id
    }
    
    func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
        guard let id = object as? Int else { return nil }
        return data.first { $0.id == id }
    }
}


extension ViewController: NSOutlineViewDelegate {
    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        guard let node = item as? Node else {
            preconditionFailure("Invalid data item \(item)")
        }
        let view = outlineView.makeView(withIdentifier: nodeCellIdentifier, owner: self) as? NSTableCellView
        view?.textField?.stringValue = node.name
        view?.imageView?.image = NSImage(systemSymbolName: node.icon, accessibilityDescription: nil)
        return view
    }
}


final class Node {
    let id: Int
    let name: String
    let icon: String
    
    init(_ name: String, _ id: Int, _ icon: String = "folder") {
        self.id = id
        self.name = name
        self.icon = icon
    }
}

private let nodeCellIdentifier = NSUserInterfaceItemIdentifier("DataCell")

Остались ли какие-нибудь разработчики для Mac, которые могут помочь?

1 ответ

Решение

Список источников

Что такое список источников? Это NSOutlineView (который является подклассом NSTableView) со специальной обработкой. Снимок экрана Finder:

Чтобы создать список источников, все, что вам нужно сделать, это установить selectionHighlightStyle собственность .sourceList. В документации говорится:

Стиль исходного списка NSTableView. В версии 10.5 для выделения выбранных строк используется голубой градиент.

Что именно он делает? Перейдите к определению в Xcode и прочтите комментарии (не включены в документацию):

Стиль исходного списка NSTableView. В версии 10.10 и выше для выделения строк используется размытие. До этого использовался голубой градиент. Примечание. Ячейки со свойством drawsBackground должны иметь значение NO. В противном случае они будут рисовать поверх выделения, которое делает NSTableView. Установка этого стиля будет иметь побочный эффект в виде установки цвета фона на цвет фона "исходного списка". Кроме того, в NSOutlineView следующие свойства изменены, чтобы получить стандартный вид "исходного списка": indentationPerLevel, rowHeight и intercellSpacing. После вызова setSelectionHighlightStyle: при необходимости можно изменить любое из других свойств. В 10.11, если цвет фона был изменен с цвета фона "исходного списка" на другой,таблица больше не будет отображать выделение как стиль размытия исходного списка, а вместо этого будет выделять обычным синим цветом.

Поскольку вы находитесь на Биг-Суре, имейте в виду, что SelectionHighlightStyle.sourceListне рекомендуется. Следует использовать style & effectiveStyle.

Образец проекта

Xcode:

  • Новый проект
    • macOS и приложение (Storyboard и AppKit App Delegate и Swift)
  • Main.storyboard
    • Добавить элемент управления списком источников
      • Позиционировать и фиксировать ограничения
      • Установите делегат и источник данных для ViewController
      • Включить автосохранение расширенных элементов
      • Установите автосохранение на все, что хотите (у меня FinderLikeSidebar там)
        • Выбирайте с умом, потому что состояние раскрытия сохраняется в пользовательских значениях по умолчанию под NSOutlineView Items FinderLikeSidebar ключ
      • Создайте @IBOutlet var outlineView: NSOutlineView!
    • Добавить еще один вид ячейки текстовой таблицы (без изображения)
      • Установить идентификатор на GroupCell
  • ViewController.swift
    • Прокомментированный код ниже

Скриншоты

Как видите, это почти похоже на Finder - 2-й уровень все еще с отступом. Причина в том, что узел " Документы" может быть расширен (имеет дочерние элементы). Они у меня здесь, чтобы продемонстрировать автосохранение.

Просто удалите их, если хотите переместить все узлы 2-го уровня влево.

Код ViewController.swift

Об этом особо и нечего сказать, кроме - читайте комментарии:)

import Cocoa

// Sample Node class covering groups & regular items
class Node {
    let id: Int
    let title: String
    let symbolName: String?
    let children: [Node]
    let isGroup: Bool
    
    init(id: Int, title: String, symbolName: String? = nil, children: [Node] = [], isGroup: Bool = false) {
        self.id = id
        self.title = title
        self.symbolName = symbolName
        self.children = children
        self.isGroup = isGroup
    }
    
    convenience init(groupId: Int, title: String, children: [Node]) {
        self.init(id: groupId, title: title, children: children, isGroup: true)
    }
}

extension Node {
    var cellIdentifier: NSUserInterfaceItemIdentifier {
        // These must match identifiers in Main.storyboard
        NSUserInterfaceItemIdentifier(rawValue: isGroup ? "GroupCell" : "DataCell")
    }
}

extension Array where Self.Element == Node {
    // Search for a node (recursively) until a matching element is found
    func firstNode(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        for element in self {
            if try predicate(element) {
                return element
            }
            if let matched = try element.children.firstNode(where: predicate) {
                return matched
            }
        }
        return nil
    }
}

class ViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
    @IBOutlet var outlineView: NSOutlineView!
    
    let data = [
        Node(groupId: 1, title: "Favorites", children: [
            Node(id: 11, title: "AirDrop", symbolName: "wifi"),
            Node(id: 12, title: "Recents", symbolName: "clock"),
            Node(id: 13, title: "Applications", symbolName: "hammer")
        ]),
        Node(groupId: 2, title: "iCloud", children: [
            Node(id: 21, title: "iCloud Drive", symbolName: "icloud"),
            Node(id: 22, title: "Documents", symbolName: "doc", children: [
                Node(id: 221, title: "Work", symbolName: "folder"),
                Node(id: 221, title: "Personal", symbolName: "folder.badge.person.crop"),
            ])
        ]),
    ]
    
    override func viewWillAppear() {
        super.viewWillAppear()
        
        // Expanded items are saved in the UserDefaults under the key:
        //
        // "NSOutlineView Items \(autosaveName)"
        //
        // By default, this value is not present. When you expand some nodes,
        // an array with persistent objects is saved. When you collapse all nodes,
        // the array is removed from the user defaults (not an empty array,
        // but back to nil = removed).
        //
        // IOW there's no way to check if user already saw this source list,
        // modified expansion state, etc. We will use custom key for this
        // purpose, so we can expand group nodes (top level) when the source
        // list is displayed for the first time.
        //
        // Next time, we wont expand anything and will honor autosaved expanded
        // items.
        if UserDefaults.standard.object(forKey: "FinderLikeSidebarAppeared") == nil {
            data.forEach {
                outlineView.expandItem($0)
            }
            UserDefaults.standard.set(true, forKey: "FinderLikeSidebarAppeared")
        }
    }
    
    // Number of children or groups (item == nil)
    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        item == nil ? data.count : (item as! Node).children.count
    }
    
    // Child of a node or group (item == nil)
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        item == nil ? data[index] : (item as! Node).children[index]
    }
    
    // View for our node
    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        guard let node = item as? Node,
              let cell = outlineView.makeView(withIdentifier: node.cellIdentifier, owner: self) as? NSTableCellView else {
            return nil
        }
        
        cell.textField?.stringValue = node.title
        
        if !node.isGroup {
            cell.imageView?.image = NSImage(systemSymbolName: node.symbolName ?? "folder", accessibilityDescription: nil)
        }
        
        return cell
    }

    // Mark top level items as group items
    func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
        (item as! Node).isGroup
    }
    
    // Every node is expandable if it has children
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        !(item as! Node).children.isEmpty
    }
    
    // Top level items (group items) are not selectable
    func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool {
        !(item as! Node).isGroup
    }
    
    // Object to save in the user defaults (NSOutlineView Items FinderLikeSidebar)
    func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
        (item as! Node).id
    }
    
    // Find an item from the saved object (NSOutlineView Items FinderLikeSidebar)
    func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
        guard let id = object as? Int else { return nil }
        return data.firstNode { $0.id == id }
    }
}
Другие вопросы по тегам