【问题标题】:How to filter objects by string comparison in Swift如何在 Swift 中通过字符串比较过滤对象
【发布时间】:2025-12-23 06:20:12
【问题描述】:

我有一系列模型:

struct Contact {
    var givenName: String!
    var familyName: String!
    var organizationName: String!
}

我想使用 ONE UITextField 过滤这些联系人。我目前的问题是在单词之间定义并仅过滤匹配所有单词的联系人。

例如:

var contacts: [Contact] = [Contact(givenName: "David", familyName: "Seek", organizationName: "Aldi"),
                           Contact(givenName: "Johne", familyName: "Doe", organizationName: "Kaisers"),
                           Contact(givenName: "Jane", familyName: "Doe", organizationName: "Tengelmann"),
                           Contact(givenName: "Marco", familyName: "Seidl", organizationName: "Rewe"),
                           Contact(givenName: "Filip", familyName: "Halbig", organizationName: "Aldi")]

我想输入:David Aldi,只找到在 Aldi 工作的 David。我不想看到也在 Aldi 工作的 Filip。

另外,如果我输入David Do,我不想看到任何联系人,因为不应该匹配。

func getSearchResults(_ filterKey: String) {
        self.presentActivityIndicator()
        var processed: Int = 0
        self.filteredContacts.removeAll()
        DispatchQueue.global(qos: .background).async {
            for contact in self.unfilteredContacts {
                processed += 1
                let lowercasedGivenName = contact.givenName.replacingOccurrences(of: " ", with: "").lowercased()
                let lowercasedFamilyName = contact.familyName.replacingOccurrences(of: " ", with: "").lowercased()
                let lowercasedOrganizationName = contact.organizationName.replacingOccurrences(of: " ", with: "").lowercased()

                let name = lowercasedGivenName.appending(lowercasedFamilyName)

                if name.range(of: filterKey.replacingOccurrences(of: " ", with: "")) != nil {
                    if !self.filteredContacts.contains(contact) {
                        self.filteredContacts.append(contact)
                    }
                }

                for word in filterKey.components(separatedBy: " ") {
                    if lowercasedOrganizationName.range(of: word.lowercased()) != nil {
                        if !self.filteredContacts.contains(contact) {
                            self.filteredContacts.append(contact)
                        }
                    }
                }

                if processed == self.unfilteredContacts.count {
                    self.reloadTableViewInMainThread()
                }
            }
        }
    } 

这是我今天尝试的几种方法之一。但每一次尝试,我都最终过滤掉了大卫,通过输入第二个名字,我发现其他联系人与第一个名字大卫不匹配,但匹配例如部分姓氏或公司名称。

我缺少什么,最好的方法是什么?非常感谢您的帮助。

【问题讨论】:

  • 一个结构,它的成员都是可变的,隐式展开的可选项......这很令人担忧。
  • @Alexander 这不是我的最终代码。在最终代码中,它是一个 CNContact 数组。这只是操场上的一个例子

标签: swift string


【解决方案1】:

首先将filterKey 分隔成空格分隔的组件

let components = filterKey.components(separatedBy: " ")

然后使用 filter 函数和闭包语法

self.filteredContacts = contacts.filter { contact -> Bool in
    for string in components {
        if contact.givenName != string && contact.familyName != string && contact.organizationName != string {
            return false
        }
    }
    return true
}

如果联系人匹配所有组件,则闭包返回true


包装在它的函数中

func getSearchResults(_ filterKey: String) {

    let components = filterKey.components(separatedBy: " ")

    self.filteredContacts = contacts.filter { contact -> Bool in
        for string in components {
            if contact.givenName != string && contact.familyName != string && contact.organizationName != string {
                return false
            }
        }
        return true
    }
}

注意:

请,请永远不要将属性/成员声明为使用init 方法初始化的隐式解包选项。声明没有感叹号的成员是完全合法的(并且建议)。但是,如果它们应该是可选的,请将它们声明为真正的可选(问号)。

如果值不会改变,则将成员声明为常量 (let)。

struct Contact {
    let givenName: String
    let familyName: String
    let organizationName: String
}

编辑:

过滤属性包含filterKey写的联系人

self.filteredContacts  = contacts.filter { contact -> Bool in
    for string in components {
        if !contact.givenName.lowercased().contains(string) &&
            !contact.familyName.lowercased().contains(string) &&
            !contact.organizationName.lowercased().contains(string) {
                return false
        }
    }
    return true
}

过滤属性filterKey

的联系人
self.filteredContacts = contacts.filter { contact -> Bool in
    for string in components {
        if !contact.givenName.lowercased().hasPrefix(string) &&
            !contact.familyName.lowercased().hasPrefix(string) &&
            !contact.organizationName.lowercased().hasPrefix(string) {
            return false
        }
    }
    return true
}

【讨论】:

  • 为什么建议不要声明没有感叹号的成员?
  • @Macabeus Implicit unwrapped optionals 通常被建议作为一种方便的方式(我称之为 alibi)不编写初始化程序。这是一个非常坏的习惯。任何(粗心)隐式展开的可选选项如果没有任何价值,都会立即使应用程序崩溃。在 init 方法中初始化的属性/成员应该是可选的 (?) 或非可选的,仅此而已。如果是 struct IUO 无论如何都是无稽之谈,因为您可以免费获得成员初始化程序。
  • 感谢您对选项的附注。但正如我的问题评论中所述,这只是操场上的一个示例模型。在真实版本中,这些是 CNContact 对象。感谢您的帮助,我会在一分钟内查看它
  • 这里相同:我的问题是您的解决方案是,如果密钥是 EXACT,它只会返回联系人。像大卫 == 大卫。但我希望大卫被退回,即使我只输入 Dav 或 Da 或 Davi,你知道吗?
  • 您的问题不清楚属性是应该匹配包含还是开头的查询。我编辑了答案。
【解决方案2】:
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
    let keys = filterKeys.components(separatedBy: " ")
    var contactsFiltered = contacts

    keys.forEach { key in
        contactsFiltered = contactsFiltered.filter {
            $0.givenName == key || $0.familyName == key || $0.organizationName == key
        }
    }

    return contactsFiltered
}

我用空格分隔了filterKeys。然后,对于每个键,我检查该值是否存在于联系人属性中。

如果你想要一个纯函数式的解决方案,你可以使用Setintersection

func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
    let keys = filterKeys.components(separatedBy: " ")

    return contacts.filter {
        Set([$0.givenName, $0.familyName, $0.organizationName]).intersection(keys).count >= keys.count
    }
}

而且,如果你想用Mirror 提供一个疯狂的解决方案,因为在Contact 中添加新属性时,你不需要更新getSearchResults

func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
    let keys = filterKeys.components(separatedBy: " ")

    return contacts.filter {
        let stringAttr = Mirror(reflecting: $0).children.filter { ($0.value as? String) != nil }
        let contactValues = stringAttr.map { $0.value as! String }

        return Set(contactValues).intersection(keys).count >= keys.count
    }
}

谨慎使用我最后的代码(或永远不要使用它)

编辑

用于匹配键中字符串的一部分。

func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
    let keys = filterKeys.components(separatedBy: " ").map { $0.lowercased() }
    var contactsFiltered = contacts

    keys.forEach { key in
        contactsFiltered = contactsFiltered.filter {
            $0.givenName.lowercased().range(of: key) != nil ||
            $0.familyName.lowercased().range(of: key) != nil ||
            $0.organizationName.lowercased().range(of: key) != nil
        }
    }

    return contactsFiltered
}

【讨论】:

  • 非常感谢您的帮助。会在一分钟内检查出来
  • 您的解决方案对我的问题是,如果密钥是 EXACT,它只会返回联系人。像大卫 == 大卫。但我希望大卫被退回,即使我只输入 Dav 或 Da 或 Davi,你知道吗?
  • @DavidSeek 好吧......你没有在问题中说出来......无论如何,我编辑了答案。
最近更新 更多