我想在我的应用中使用以下功能:
当我在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}}
答案 0 :(得分:0)
如果您要求:“如果有人在NSTextView中输入内容,我该如何触发某个功能?”
实施NSTextViewDelegate
将NSTextView
的代表设置为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
}
}
}