I have a core data / document-driven macOS app, using Swift and I struggle on combining the out-of-the-box spell checking API with an NSDocument (NSPersistentDocument in my case)
It took me more time than it should take, but this is what I got, mostly guided by this great answer:
class VTDocument: NSPersistentDocument, NSChangeSpelling {
[...]
private let spellchecker = SpellChecker()
#IBAction func showGuessPanel(_ sender: Any?){
spellchecker.startSpellCheck(nodes: Array(db.nodes), tag: 0)
}
#IBAction #objc func changeSpelling(_ sender: Any?){
spellchecker.replace(with: "Test")
}
This is leading me to see the NSSpellChecker.spellingPanel, correctly showing the word to correct. However, the changeSpelling function should be "called" by the panel but is never called. The above spellChecker is a simple wrapper around the NSSpellChecker that keeps the status between function calls.
The SpellChecker class looks like this.
import Cocoa
class SpellChecker {
let checker = NSSpellChecker.shared
let count: UnsafeMutablePointer<Int> = UnsafeMutablePointer<Int>.allocate(capacity: 1)
var nodes = Array<Node> ()
var nodeNr = 0
var stringPos = 0
var range: NSRange = NSRange()
func startSpellCheck(nodes: [Node], tag: Int ) {
self.nodes = nodes
nodeNr = 0
stringPos = 0
continueChecking()
}
func continueChecking(){
if nodes.count == 0 {
return
}
if nodeNr >= nodes.count {
checker.updateSpellingPanel(withMisspelledWord: "")
checker.spellingPanel.orderFront(self)
return
}
if let nodeText = nodes[nodeNr].label {
range = checker.checkSpelling(of: nodeText, startingAt: stringPos, language: nil, wrap: false, inSpellDocumentWithTag: 0, wordCount: count)
if count.pointee > 0 {
stringPos = range.lowerBound
checker.updateSpellingPanel(withMisspelledWord: nodeText[range])
checker.spellingPanel.orderFront(self)
return
}
}
nodeNr = nodeNr + 1
continueChecking()
}
func replace(with: String){
if let nodeText = nodes[nodeNr].label {
let text = nodeText as NSString
text.replacingCharacters(in: range, with: with)
nodes[nodeNr].label = text as String
}
}
}
In Swift 2 the following code was working:
let request = NSFetchRequest(entityName: String)
but in Swift 3 it gives error:
Generic parameter "ResultType" could not be inferred
because NSFetchRequest is now a generic type. In their documents they wrote this:
let request: NSFetchRequest<Animal> = Animal.fetchRequest
so if my result class is for example Level how should I request correctly?
Because this not working:
let request: NSFetchRequest<Level> = Level.fetchRequest
let request: NSFetchRequest<NSFetchRequestResult> = Level.fetchRequest()
or
let request: NSFetchRequest<Level> = Level.fetchRequest()
depending which version you want.
You have to specify the generic type because otherwise the method call is ambiguous.
The first version is defined for NSManagedObject, the second version is generated automatically for every object using an extension, e.g:
extension Level {
#nonobjc class func fetchRequest() -> NSFetchRequest<Level> {
return NSFetchRequest<Level>(entityName: "Level");
}
#NSManaged var timeStamp: NSDate?
}
The whole point is to remove the usage of String constants.
I think i got it working by doing this:
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Level")
at least it saves and loads data from DataBase.
But it feels like it is not a proper solution, but it works for now.
The simplest structure I found that works in 3.0 is as follows:
let request = NSFetchRequest<Country>(entityName: "Country")
where the data entity Type is Country.
When trying to create a Core Data BatchDeleteRequest, however, I found that this definition does not work and it seems that you'll need to go with the form:
let request: NSFetchRequest<NSFetchRequestResult> = Country.fetchRequest()
even though the ManagedObject and FetchRequestResult formats are supposed to be equivalent.
Here are some generic CoreData methods that might answer your question:
import Foundation
import Cocoa
func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
let entityName = T.description()
let context = app.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
let record = T(entity: entity!, insertInto: context)
return record
}
func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
let recs = allRecords(T.self)
return recs.count
}
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let predicate = search
{
request.predicate = predicate
}
if let sortDescriptors = multiSort
{
request.sortDescriptors = sortDescriptors
}
else if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func deleteRecord(_ object: NSManagedObject)
{
let context = app.managedObjectContext
context.delete(object)
}
func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
let context = app.managedObjectContext
let results = query(T.self, search: search)
for record in results
{
context.delete(record)
}
}
func saveDatabase()
{
let context = app.managedObjectContext
do
{
try context.save()
}
catch
{
print("Error saving database: \(error)")
}
}
Assuming that there is a NSManagedObject setup for Contact like this:
class Contact: NSManagedObject
{
#NSManaged var contactNo: Int
#NSManaged var contactName: String
}
These methods can be used in the following way:
let name = "John Appleseed"
let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name
let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %#", name))
for contact in contacts
{
print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}
deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %#", name))
recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")
saveDatabase()
This is the simplest way to migrate to Swift 3.0, just add <Country>
(tested and worked)
let request = NSFetchRequest<Country>(entityName: "Country")
Swift 3.0 This should work.
let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
I also had "ResultType" could not be inferred errors. They cleared once I rebuilt the data model setting each entity's Codegen to "Class Definition". I did a brief writeup with step by step instructions here:
Looking for a clear tutorial on the revised NSPersistentContainer in Xcode 8 with Swift 3
By "rebuilt" I mean that I created a new model file with new entries and attributes. A little tedious, but it worked!
What worked best for me so far was:
let request = Level.fetchRequest() as! NSFetchRequest<Level>
I had the same issue and I solved it with the following steps:
Select your xcdatamodeld file and go to the Data Model Inspector
Select your first Entity and go to Section class
Make sure that Codegen "Class Definition" is selected.
Remove all your generated Entity files. You don't need them anymore.
After doing that I had to remove/rewrite all occurences of fetchRequest as XCode seem to somehow mix up with the codegenerated version.
HTH
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func loadItemsCategory() {
let request: NSFetchRequest<Category> = Category.fetchRequest()
do {
categoryArray = try context.fetch(request)
} catch {
print(error)
}
tableView.reloadData()
}
func backBtnAction(){
var index = collectionView?.indexPathsForSelectedItems() as! [NSIndexPath]
collectionView?.scrollEnabled = true
collectionView?.reloadItemsAtIndexPaths(index)
}
how to get indexpath in the collectionview... like I am doing
var index = collectionView?.indexPathsForSelectedItems() as! [NSIndexPath]
indexPathsForSelectedItems() returns [NSIndexPath]? so the usual way is optional binding without casting
func backBtnAction()
{
if let indexes = collectionView?.indexPathsForSelectedItems() {
collectionView!.scrollEnabled = true
collectionView!.reloadItemsAtIndexPaths(indexes)
}
}
I'm using this class that was written in Swift 1.2 and now I want to use it with Swift 2.0.
I get an error: Ambiguous use of 'subscript' # let artist = result["name"] as! String
} else if let jsonArtists = jsonResult["artists"] as? NSDictionary {
if let results:NSArray = jsonArtists["items"] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.searching = false
var suggestionResults: [spotifySearchResult] = []
for result in results {
let artist = result["name"] as! String
var sugresult = spotifySearchResult()
sugresult.artist = artist
if !suggestionResults.contains(sugresult) {
suggestionResults.append(sugresult)
}
}
handler(suggestionResults)
})
}
}
}
I tried different fixes such as let artist = (result["name"] as! String) or let artist = (result["name"] as! String) as! String
But nothing worked.
I know that the question was already post 4 times but, I can't find anyone explaining how to fix it in every case, only case by case.
Can someone explain me how to investigate to fix it? Not just only a fix for my case. I would prefer fix it by myself with your hints!
BTW what does mean subscript? Is subscript the thing between quotation mark? IMHO the error message is a bit vague...
EDIT:
class spotifySearchResult : NSObject {
var artist=""
var track=""
var duration=0
var spotifyURL = NSURL()
var spotifyURI = NSURL()
override func isEqual(theObject: AnyObject?) -> Bool {
if let myObject = theObject as? spotifySearchResult {
return (myObject.artist.uppercaseString == self.artist.uppercaseString && myObject.track.uppercaseString == self.track.uppercaseString)
}
return false
}
}
Subscription means to use the shorter syntax item["key"] for item.objectForKey["key"]
results seems to be an array of dictionaries so I suggest to cast down to a more specific type
if let results = jsonArtists["items"] as? [[String:AnyObject]] {
or even, if all values are guaranteed to be strings
if let results = jsonArtists["items"] as? [[String:String]] {
I just updated to Xcode7 and am trying to switch my project to using the Swift 2.0 Syntax when I ran into this error in a file from an open source library I'm using. Here's the relevant code:
public lazy var cookies:[String:NSHTTPCookie] = {
let foundCookies: [NSHTTPCookie]
if let responseHeaders = (self.response as? NSHTTPURLResponse)?.allHeaderFields {
foundCookies = NSHTTPCookie.cookiesWithResponseHeaderFields(responseHeaders, forURL:NSURL(string:"")!) as! [NSHTTPCookie]
} else {
foundCookies = []
}
var result:[String:NSHTTPCookie] = [:]
for cookie in foundCookies {
result[cookie.name] = cookie
}
return result
}()
The error reads: Cannot assign a value of type '[NSHTTPCookie]' to a value of type '[NSHTTPCookie]'
Is there something I'm missing here?
Change your code to this:
public lazy var cookies:[String:NSHTTPCookie] = {
let foundCookies: [NSHTTPCookie]
if let responseHeaders = (self.response as? NSHTTPURLResponse)?.allHeaderFields as? [String:String] {
foundCookies = NSHTTPCookie.cookiesWithResponseHeaderFields(responseHeaders, forURL:NSURL(string:"")!)
} else {
foundCookies = []
}
var result:[String:NSHTTPCookie] = [:]
for cookie in foundCookies {
result[cookie.name] = cookie
}
return result
}()
Changes:
if let responseHeaders ... line - did add as? [String:String], because allHeadersFields return type is [NSObject : AnyObject] and not [String:String] required by cookiesWithResponseHeaderFields...
removed as! [NSHTTPCookie] - it has no sense, because cookiesWithResponseHeaderFields return type is already [NSHTTPCookie]
Just check cookiesWithResponseHeaderFields signature:
class func cookiesWithResponseHeaderFields(headerFields: [String : String],
forURL URL: NSURL) -> [NSHTTPCookie]
Please read How do I ask a good question. At least, you should point out to lines where the problem is, etc.