NSCollectionViewItems отображается неправильно после обновления данных и вызова NSCollectionView.reloadData () - PullRequest
0 голосов
/ 17 сентября 2018

У меня есть простое приложение, которое содержит поиск и список элементов (NSCollectionView).Когда пользователь вводит что-то в поиске, я фильтрую массив данных и повторно отображаю список элементов, вызывая NSCollectionView.reloadData(), но он отображает некорректно.

Вот пример того, как это работает прямо сейчас:

Исходное состояние при запуске приложения (посмотрите на 5-й элемент, у него есть заголовок, текст и ссылка):

После ввода 'testв поиске.Посмотрите на предмет, заголовок и ссылка пропали:

Также, если мы проверим иерархию, NSCollectionView будет иметь 6 элементов, как в исходном состоянии:

Вот мой ViewController.swift:

import Cocoa
import PureLayout

class ViewController: NSViewController {
    let searchView = NSView()
    let search = NSTextField()
    var searchingString: String?

    let cardList = NSCollectionView()

    let cardId = NSUserInterfaceItemIdentifier("CardID")

    let cardsData: [(type: String, label: String?, text: String, link: String?, tags: [String])] = [
        (
            type: "text",
            label: nil,
            text: "Alternatively one can use paddle.com - They charge a higher fee, tho.",
            link: nil,
            tags: ["#text"]
        ),
        (
            type: "text",
            label: "Essential YC Advice",
            text:
                """
                1. Запускайся как можно раньше. Запустить посредственный продукт и постепенно изменять его через общение с пользователями лучше, чем в одиночку доводить свой проект до «идеального».
                2. Работай над тем, что действительно нужно людям.
                3. Делай вещи, которые не масштабируются.
                4. Найди решение «90/10». 90/10 — это когда ты можешь на 90% решить проблему пользователя, приложив всего 10% усилий.
                5. Найди 10-100 пользователей, которым по-настоящему нужен твой продукт. Маленькая группа людей, которые тебя любят, лучше большой группы, которым ты просто нравишься.
                6. На каком-то этапе каждый стартап имеет нерешенные проблемы. Успех определяется не тем, есть ли у тебя изначально такие проблемы, а что ты делаешь, чтобы их как можно быстрее решить.
                7. Не масштабируй свою команду и продукт, пока ты не построил что-то, что нужно людям.
                8. Стартапы в любой момент времени могут хорошо решать только одну проблему.
                9. Игнорируй своих конкурентов. В мире стартапов ты скорее умрешь от суицида, чем от убийства.
                10. Будь хорошим человеком. Ну или хотя бы просто не будь придурком.
                11. Спи достаточное количество времени и занимайся спортом.

                Многие пункты подробно расписаны здесь:
                http://blog.ycombinator.com/ycs-essential-startup-advice/
                """,
            link: nil,
            tags: ["#text", "#advice", "#launch", "#startup_hero", "#telegram_channel"]
        ),
        (
            type: "text",
            label: nil,
            text:
                """
                If I was you, I'd start trying to build a small following.
                it helps a lot (especially if your making products for makers)

                Indie Hackers / Reddit Startup + Side Project great places ...
                And slowly but surely you'll build twitter + email list.
                follow @AndreyAzimov example! with his medium posts + twitter
                """,
            link: nil,
            tags: ["#text", "#advice"]
        ),
        (
            type: "link",
            label: "Paddle",
            text: "Like Stripe but maybe avaliable in Uzbekistan and Russia",
            link: "https://paddle.com/",
            tags: ["#link", "#payment", "#payment_service"]
        ),
        (
            type: "link",
            label: "How to Test and Validate Startup Ideas: – The Startu...",
            text: "One of the first steps to launching a successful startup is building your Minimal Viable Product. In essence, a Minimal Viable Product (MVP) is all about verifying the fact that there is a market…",
            link: "https://medium.com/swlh/how-to-start-a-startup-e4f002ff3ee1",
            tags: ["#link", "#article", "#medium", "#read_later", "#validating", "#validating_idea"]
        ),
        (
            type: "link",
            label: "The best Slack groups for UX designers – UX Collect...",
            text: "Tons of companies are using Slack to organize and facilitate how their employees communicate on a daily basis. Slack has now more than 5 million daily active users and more than 60,000 teams around…",
            link: "https://uxdesign.cc/the-best-slack-groups-for-ux-designers-25c621673d9c",
            tags: ["#link", "#article", "#medium", "#read_later", "#ux", "#community"]
        )
    ]

    var filteredCards: [(type: String, label: String?, text: String, link: String?, tags: [String])] = []

    func filterCards() {
        filteredCards = cardsData.filter { cardData in
            var isValidByTags = true
            var isValidByWords = true

            if let searchingArray = searchingString?.split(separator: " ") {
                let searchingTags = searchingArray.filter { $0.hasPrefix("#") }
                let searchingWords = searchingArray.filter { !$0.hasPrefix("#") }

                searchingTags.forEach { searchingTag in
                    var isValidTag = false

                    cardData.tags.forEach { cardDataTag in
                        if cardDataTag.lowercased() == searchingTag.lowercased() {
                            isValidTag = true
                        }
                    }

                    if !isValidTag {
                        isValidByTags = false
                    }
                }

                searchingWords.forEach { searchingWord in
                    if !(cardData.label != nil && (cardData.label?.lowercased().contains(searchingWord.lowercased()))!) && !cardData.text.lowercased().contains(searchingWord.lowercased()) {
                        isValidByWords = false
                    }
                }
            }

            return isValidByTags && isValidByWords
        }
    }

    func configureSearch() {
        search.placeholderString = "Enter one or more tags devided by space and some words. For example, #link #work Design"
        search.layer = CALayer()
        search.delegate = self

        search.layer?.backgroundColor = .white
        search.layer?.cornerRadius = 16

        searchView.addSubview(search)

        search.autoPinEdge(toSuperviewEdge: .top, withInset: 10)
        search.autoPinEdge(toSuperviewEdge: .right, withInset: 20)
        search.autoPinEdge(toSuperviewEdge: .bottom, withInset: 10)
        search.autoPinEdge(toSuperviewEdge: .left, withInset: 20)

        view.addSubview(searchView)

        searchView.autoSetDimension(.height, toSize: 50)
        searchView.autoPinEdge(toSuperviewEdge: .top)
        searchView.autoPinEdge(toSuperviewEdge: .right)
        searchView.autoPinEdge(.bottom, to: .top, of: cardList)
        searchView.autoPinEdge(toSuperviewEdge: .left)
    }

    func configureCardList() {
        let flowLayout = NSCollectionViewFlowLayout()
        flowLayout.minimumInteritemSpacing = 0
        flowLayout.minimumLineSpacing = 0

        cardList.collectionViewLayout = flowLayout

        cardList.frame = NSRect(x: 0, y: 0, width: 1200, height: 628)
        cardList.delegate = self
        cardList.dataSource = self

        let nib = NSNib(nibNamed: NSNib.Name(rawValue: "Card"), bundle: nil)
        cardList.register(nib, forItemWithIdentifier: cardId)

        view.addSubview(cardList)

        cardList.autoPinEdge(toSuperviewEdge: .right)
        cardList.autoPinEdge(toSuperviewEdge: .bottom)
        cardList.autoPinEdge(toSuperviewEdge: .left)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        filterCards()

        configureCardList()
        configureSearch()
    }
}

extension ViewController: NSCollectionViewDataSource, NSCollectionViewDelegate, NSCollectionViewDelegateFlowLayout, NSTextFieldDelegate {
    func collectionView(_ cardList: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
        return filteredCards.count
    }

    func collectionView(_ cardList: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
        let card = cardList.makeItem(withIdentifier: cardId, for: indexPath) as! Card

        card.data = filteredCards[indexPath.item]

        return card
    }

    func collectionView(_ cardList: NSCollectionView, layout cardListLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
        return CGSize(width: 240, height: 240)
    }

    override func controlTextDidChange(_ obj: Notification) {
        searchingString = search.stringValue

        filterCards()

        cardList.reloadData()
    }
}

Вот мой Card.swift:

import Cocoa
import PureLayout

class Card: NSCollectionViewItem {
    let labelView = NSTextView()
    let linkView = NSTextView()
    let textView = NSTextView()

    var data: (type: String, label: String?, text: String, link: String?, tags: [String])?

    func configureLabelView() {
        labelView.font = NSFont(name: "SF Pro Display Medium", size: 15)
        labelView.isEditable = false
        labelView.isFieldEditor = false
        labelView.isHidden = false

        labelView.string = (data?.label)!

        labelView.autoSetDimension(.height, toSize: 16)
        labelView.autoPinEdge(toSuperviewEdge: .top, withInset: 10)
        labelView.autoPinEdge(toSuperviewEdge: .right, withInset: 10)
        labelView.autoPinEdge(toSuperviewEdge: .left, withInset: 10)
    }

    func configureLinkView() {
        linkView.font = NSFont(name: "SF Pro Display", size: 13)
        linkView.isEditable = false
        linkView.isFieldEditor = false
        linkView.isHidden = false

        linkView.string = (URL(string: (data?.link)!)?.host)!

        let attributedString = NSMutableAttributedString(string: linkView.string)
        let range = NSRange(location: 0, length: linkView.string.count)
        let url = URL(string: (data?.link)!)

        attributedString.setAttributes([.link: url!], range: range)
        linkView.textStorage?.setAttributedString(attributedString)

        linkView.linkTextAttributes = [.underlineStyle: NSUnderlineStyle.styleSingle.rawValue]

        linkView.autoSetDimension(.height, toSize: 16)
        linkView.autoPinEdge(toSuperviewEdge: .right, withInset: 10)
        linkView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 10)
        linkView.autoPinEdge(toSuperviewEdge: .left, withInset: 10)
    }

    func configureTextView() {
        textView.font = NSFont(name: "SF Pro Display", size: 13)
        textView.textContainer?.lineBreakMode = .byTruncatingTail
        textView.isEditable = false
        textView.isFieldEditor = false

        textView.string = (data?.text)!

        if data?.label != nil && (data?.label?.count)! > 0 {
            configureLabelView()

            textView.autoPinEdge(.top, to: .bottom, of: labelView, withOffset: 10)
        } else {
            textView.autoPinEdge(toSuperviewEdge: .top, withInset: 10)
        }

        if data?.link != nil && (data?.link?.count)! > 0 && data?.type == "link" {
            configureLinkView()

            textView.autoPinEdge(.bottom, to: .top, of: linkView, withOffset: -10)
        } else {
            textView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 10)
        }

        textView.autoPinEdge(toSuperviewEdge: .right, withInset: 10)
        textView.autoPinEdge(toSuperviewEdge: .left, withInset: 10)
    }

    override func viewDidAppear() {
        super.viewDidAppear()

        view.layer?.borderWidth = 1
        view.layer?.borderColor = .black

        view.addSubview(labelView)
        view.addSubview(textView)
        view.addSubview(linkView)

        labelView.isHidden = true
        linkView.isHidden = true

        configureTextView()
    }
}

Также я получаю такие ошибки вконсоль:

Set the NSUserDefault NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints to YES to have -[NSWindow visualizeConstraints:] automatically called when this happens.  And/or, set a symbolic breakpoint on LAYOUT_CONSTRAINTS_NOT_SATISFIABLE to catch this in the debugger.
2018-09-17 20:38:25.505130+0500 Taggy[1317:23304] [Layout] Unable to simultaneously satisfy constraints:
(
    "<NSLayoutConstraint:0x60400009e050 NSTextView:0x600000121ae0.bottom == NSView:0x604000121ae0.bottom - 10   (active)>",
    "<NSLayoutConstraint:0x600000095b80 NSTextView:0x600000121a40.bottom == NSTextView:0x600000121ae0.top - 10   (active)>",
    "<NSLayoutConstraint:0x600000092c00 NSTextView:0x600000121a40.bottom == NSView:0x604000121ae0.bottom - 10   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x60400009e050 NSTextView:0x600000121ae0.bottom == NSView:0x604000121ae0.bottom - 10   (active)>

Set the NSUserDefault NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints to YES to have -[NSWindow visualizeConstraints:] automatically called when this happens.  And/or, set a symbolic breakpoint on LAYOUT_CONSTRAINTS_NOT_SATISFIABLE to catch this in the debugger.

Вот ссылка на GitHub, если вы хотите поиграть - https://github.com/SilencerWeb/taggy-app/tree/development

Любая помощь будет приветствоваться, я пытаюсь решить эту проблему для2 дня: (

1 Ответ

0 голосов
/ 24 сентября 2018

Решено путем регистрации Карты как класса вместо пера.

До:

let nib = NSNib(nibNamed: NSNib.Name(rawValue: "Card"), bundle: nil)
cardList.register(nib, forItemWithIdentifier: cardId)

После того, как:

cardList.register(Card.self, forItemWithIdentifier: cardId)
...