如何在键入字符串搜索时在另一个字符串中查找一个字符串的所有位置?

时间:2015-10-01 14:23:25

标签: string macos swift cocoa

我想在我的应用中使用以下功能:

当我在TableView中为每个酶同时在NSTextView窗口中键入DNA序列(字符串)时(每个酶代表小字符串),用户立即看到每个酶对应的找到的位点数(字符串)( 0或任何数字)。

我有一个函数,我可以用它来查找字符串中字符串的所有可能位置(返回NSRanges数组)。在我的情况下,这将是在DNA序列(字符串)中找到对应于每种酶的所有可能位点(字符串NSRanges)。

因此,再一次,问题是如何实现这个功能:在键入字符串以查找此字符串中的所有站点(以NSRanges数组的形式)并将相应的数字放入表中酶。

换句话说,返回NSRanges数组的酶位点位置的函数应该自动启动。

更新

我是cocoa的新手,经过 R Menke的建议(我在代码中将代码行放在下面)我有更多可能是愚蠢的问题。我有一个控制器类作为NSWindowController的子类。我无法将 R Menke 的代码放到此类中(请参阅下面的错误)。并且,在我的控制器类中,我有我的NSTextView,用户将文本键入@IBOutlet,我应该使用它吗?我应该制作另一个控制器文件?下面的代码和错误。

import Cocoa

class AllUnderControl: NSWindowController, NSTextViewDelegate
{
override var windowNibName: String?
{
return "AllUnderControl"
}

override func windowDidLoad() {
    super.windowDidLoad()
    inputDnaFromUser.delegate = self
}

func textDidChange(notification: NSNotification) {

    // trigger your function
}

@IBOutlet var inputDnaFromUser: NSTextView! = NSTextView(frame: CGRectZero)

更新2 在阅读了两个控制器的描述之后:NSWindowController和NSViewController我在下面进行了以下更改。触发功能是否正确?

{{1}}

2 个答案:

答案 0 :(得分:0)

如果您要求:“如果有人在NSTextView中输入内容,我该如何触发某个功能?”

实施NSTextViewDelegateNSTextView的代表设置为self并在textDidChange内触发您的功能。 NSTextViewDelegate具有一组将由用户交互触发的功能。因此,当相应的动作发生时,它们内部的代码将被执行。

我建议使用NSViewController,而不是NSWindowController。 NSWindowController用于管理NSViewControllers。 NSViewControllers是按钮和文本字段等更好的地方。

NSWindowController vs NSViewController

class ViewController: NSViewController, NSTextViewDelegate {


    @IBOutlet var inputDnaFromUser: NSTextView!


    override func viewDidLoad() {
        super.viewDidLoad()

        inputDnaFromUser.delegate = self

    }

    override var representedObject: AnyObject? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    func textDidChange(notification: NSNotification) {
        print("editing stuff")
    }
}

如果您要问:“如何在另一个字符串中找到所有出现的字符串?”

这将返回一系列范围。该数组的计数显然是出现次数。

另一种选择是使用enumerateSubstringsInRange,如@Russel在答案中所述。我总是喜欢写自己的循环。

let string = "The sky is blue today, super blue"
let searchString = "blue"

var ranges: [NSRange] = []

var copyString = NSMutableString(string: string)

while copyString.containsString(searchString) {
    ranges.append(copyString.rangeOfString(searchString))

    guard let lastRange = ranges.last else {
        break
    }
    var replaceString = ""
    for _ in 0..<lastRange.length { replaceString += "$" } // unnalowed character

    copyString.replaceCharactersInRange(lastRange, withString: replaceString)

}

根据评论中的建议: 一种更快的方法。

let string : NSString = "The sky is blue today, super blue"
let searchString = "blue"

var ranges: [NSRange] = []

var searchRange : NSRange = NSRange(location: 0, length: string.length)
var lastFoundRange : NSRange = string.rangeOfString(searchString, options: NSStringCompareOptions.LiteralSearch, range: searchRange)

while lastFoundRange.location != NSNotFound {

    ranges.append(lastFoundRange)

    let searchRangeLocation = lastFoundRange.location + lastFoundRange.length
    let searchRangeLength = string.length - searchRangeLocation
    searchRange = NSRange(location: searchRangeLocation, length: searchRangeLength)

    lastFoundRange = string.rangeOfString(searchString, options: NSStringCompareOptions.LiteralSearch, range: searchRange)
}

您将希望在后台队列中执行此操作。但这很快变得棘手。一种酶可以是一种时刻而且会改变下一种。因此,您需要对键入的每个字符进行所有工作。

一种可能的解决方案是在键入字符时取消每个正在进行的搜索。如果在输入下一个字符之前完成,则会得到结果。

这是我写的iOS word highlighter实现了这个逻辑。除了使用UIColor外,一切都是纯粹的基础。很容易将其改为Cocoa。

答案 1 :(得分:0)

您需要实现UISearchResultsUpdating协议才能实现此目的。它使用UISearchController(在iOS 8中引入),必须以编程方式而不是通过故事板添加,但不要担心,它非常简单。

使用updateSearchResultsForSearchController委托方法处理搜索,该方法在搜索栏文本发生更改时调用。我试图让它保持自我记录,但如果您有任何问题,请告诉我。根据您需要搜索多少酶,这可能会很快变得低效,因为您必须搜索每种酶的子串出现。

干杯, 罗素

class YourTableViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
    // Array of searchable enzymes
    var enzymes: [String] = ["...", "...", "..."]

    // Dictionary of enzymes mapping to an array of NSRange 
    var enzymeSites: [String : [NSRange]] = [String : [NSRange]]()

    // Search controller
    var enzymeSearchController = UISearchController()

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        self.enzymeSearchController = UISearchController(searchResultsController: nil)
        self.enzymeSearchController.dimsBackgroundDuringPresentation = true

        // This is used for dynamic search results updating while the user types
        // Requires UISearchResultsUpdating delegate
        self.enzymeSearchController.searchResultsUpdater = self

        // Configure the search controller's search bar
        self.enzymeSearchController.searchBar.placeholder = "Enter DNA sequence"
        self.enzymeSearchController.searchBar.sizeToFit()
        self.enzymeSearchController.searchBar.delegate = self
        self.definesPresentationContext = true

        // Set the search controller to the header of the table
        self.tableView.tableHeaderView = self.enzymeSearchController.searchBar
    }

    // MARK: - Search Logic

    func searchEnzymeSites(searchString: String) {

        // Search through all of the enzymes
        for enzyme in enzymes {
            // See logic from here: https://stackoverflow.com/questions/27040924/nsrange-from-swift-range
            let nsEnzyme = searchString as NSString
            let enzymeRange = NSMakeRange(0, nsEnzyme.length)

            nsEnzyme.enumerateSubstringsInRange(enzymeRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in

                if (substring == enzyme) {
                    // Update the enzymeSites dictionary by appending to the range array
                    enzymeSites[enzyme]?.append(substringRange)
                }
            })
        }
    }

    // MARK: - Search Bar Delegate Methods

    func searchBarSearchButtonClicked(searchBar: UISearchBar) {

        // Force search if user pushes button
        let searchString: String = searchBar.text.lowercaseString
        if (searchString != "") {
            searchEnzymeSites(searchString)
        }
    }

    func searchBarCancelButtonClicked(searchBar: UISearchBar) {

        // Clear any search criteria
        searchBar.text = ""

        // Force reload of table data from normal data source
    }

    // MARK: - UISearchResultsUpdating Methods

    // This function is used along with UISearchResultsUpdating for dynamic search results processing
    // Called anytime the search bar text is changed
    func updateSearchResultsForSearchController(searchController: UISearchController) {

        let searchString: String = searchController.searchBar.text.lowercaseString
        if (searchString != "") {
            searchEnzymeSites(searchString)
        }
    }

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if (self.enzymeSearchController.active) {
            return self.enzymeSites.count
        } else {
            // return whatever your normal data source is
        }
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("userCell") as! UserCell

        if (self.enzymeSearchController.active && self.enzymeSites.count > indexPath.row) {
            // bind data to the enzymeSites cell
        } else {
            // bind data from your normal data source
        }

        return cell
    }

    // MARK: - UITableViewDelegate

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        tableView.deselectRowAtIndexPath(indexPath, animated: true)

        if (self.enzymeSearchController.active && self.searchUsers.count > 0) {
            // Segue or whatever you want
        } else {
            // normal data source selection
        }
    }
}