Sort NSArray with sortedArrayUsingComparator - sorting

In Objective-C I can sort an NSArray using this statement:
NSArray *sortedArray = [persons sortedArrayUsingComparator:^NSComparisonResult(Person *p1, Person *p2) {
return [p1.name compare:p2.name];
}];
I'm unable to reproduce the same statement with Swift. All I found was using Array.

You can either use Swift's built in sort functions or, since a Swift array is bridged to NSArray you can call sortedArrayUsingComparator from swift directly.
Using Swift's sorted function:
var sortedArray = sorted(persons) {
(obj1, obj2) in
// The downcast to Person is only needed if persons is an NSArray or a Swift Array of AnyObjects
let p1 = obj1 as Person
let p2 = obj2 as Person
return p1.name < p2.name
}
Or, using NSArray's sortedArrayUsingComparator:
var sortedArray = persons.sortedArrayUsingComparator {
(obj1, obj2) -> NSComparisonResult in
let p1 = obj1 as Person
let p2 = obj2 as Person
let result = p1.name.compare(p2.name)
return result
}

There's no need to avoid Swift Array.
It's bridged in both directions with NSArray, so for more type-safe code it's best to do your work with Swift arrays and bridge only when needed for interoperating with ObjC APIs. (And in most imported APIs, Swift automatically converts NSArray to [AnyObject], so you don't even need to bridge very often.)
Assuming the persons array is an [AnyObject] you got from other API, you can cut down on the amount of type casting relative to other answers by casting the array first:
let sortedPersons = sorted(persons as [Person]) { $0.name < $1.name }
// sortedPersons has inferred type [Person]
Also, since you're using a comparator block only to sort on a specific property of your Person class, you might do better to use sort descriptors:
let sortedPersons = (persons as NSArray).sortedArrayUsingDescriptors([
NSSortDescriptor(key: "name", ascending: true)
])
(The persons as NSArray part may not be necessary if persons came from an ObjC API.)
Depending on how the Person class is implemented, sorting with descriptors can produce a more efficient sort on the backend. For example, if it's a Core Data managed object, sorting with descriptors might produce a SQL query that executes fast in the database and uses little memory, while sorting with a comparator closure requires instantiating every object from the database just to evaluate the closure against each.

So try just write the same with Swift:
var sortedArray:NSArray =
persons.sortedArrayUsingComparator(){(p1:AnyObject!, p2:AnyObject!) -> NSComparisonResult in
if (p1 as Person).name > (p2 as Person).name {
return NSComparisonResult.OrderedDescending
}
if (p1 as Person).name < (p2 as Person).name {
return NSComparisonResult.OrderedAscending
}
return NSComparisonResult.OrderedAscending
}
Test (Playground):
class Person{
var name:String?
}
var p1:Person = Person()
p1.name = "a"
var p3:Person = Person()
p3.name = "c"
var p2:Person = Person()
p2.name = "b"
var persons:NSArray = [p1,p3,p2]
var sortedArray:NSArray = persons.sortedArrayUsingComparator(){
(p1:AnyObject!, p2:AnyObject!) -> NSComparisonResult in
if (p1 as Person).name > (p2 as Person).name {
return NSComparisonResult.OrderedDescending
}
if (p1 as Person).name < (p2 as Person).name {
return NSComparisonResult.OrderedAscending
}
return NSComparisonResult.OrderedAscending
}
for item:AnyObject in sortedArray{
println((item as Person).name)
}
Output:
Optional("a")
Optional("b")
Optional("c")

In Swift you can use new approach. Try to use sorted method instead sortedArrayUsingComparator like this...
var sortedArray = results.sorted {
(obj1, obj2) -> Bool in
return obj1.count! > obj2.count!
}

in case you have custom object and want to sort by date
let sortedArray = self.posts.sorted {
(obj1, obj2) -> Bool in
return (obj1 as! PostModel).orderCancellionDate.compare( (obj2 as! PostModel).orderCancellionDate) == .orderedDescending
}

Related

Xcode Coredata: Fetch two values from one CoreData entity and insert to another CoreData entity within loop function

Im very new with CoreData fetching/display and so far able to save into CoreData from a JSON fetch.
The fetched data is an array of Airport info with only three items; airport_code, access_point and image_url.
I need to add two more values to each fetched item - a lat and lon coordinate which is stored in another CoreData entity with a matching airport_code item/attribute.
Can anyone provide some guidance as to how to create a separate function to query this other CoreData during the loop sequence by using the predicate value of the airport_code? I have attached the code I have so far:
func saveData(context: NSManagedObjectContext){
xArray.forEach { (data) in
let entity = Airports(context: context)
entity.airport_code = data.airport_code
entity.access_points = data.access_points
entity.image_url = data.image_url
entity.lat = getLat()
entity.lon = getLon()
}
do{
try context.save()
print("Success Saving to CoreData: \(xArray.count)")
}
catch{
print("Error Saving to CoreData \(error.localizedDescription)")
}
}
func getLat() -> String {
#FetchRequest(entity: AllAirports.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \AllAirports.airport_code, ascending: true)])
var results: FetchedResults<AllAirports>
//this is where Im lost as to how to query this CoreData to fetch the LON value when there is a match to the data.airport_code in the loop above.
return latResults
}
func getLon() -> String {
#FetchRequest(entity: AllAirports.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \AllAirports.airport_code, ascending: true)])
var results: FetchedResults<AllAirports>
//this is where Im lost as to how to query this CoreData to fetch the LON value when there is a match to the data.airport_code in the loop above.
return lonResults
}
func fetchData(context: NSManagedObjectContext) {
// this function performs a JSON parse and returns the xArray above
….
First you want to take a look at the response here What is the best way to do a fetch request in CoreData? to get an idea how a programmatic fetch request may look like.
As I understand your problem now, you want to write two methods getLat() and getLon() which are going to fetch the coordinates for an airport.
I would recommend to write a single function as shown below which looks up your airport in AllAirports using the given code and returns the tuple with the coordinates found.
func getCoord(airport_code: String) -> (lon: Double, lat: Double)? {
let request: NSFetchRequest<AllAirports> = AllAirports.fetchRequest()
request.predicate = NSPredicate(format: "airport_code == %#", airport_code)
if let result = try? viewContext.fetch(request) {
print("Found \(result.count) airports matching \(airport_code)")
// Just return the first matched airport
if let first = result.first {
return (first.longitude, first.latitude)
}
}
return nil
}
A better solution would be to add an extension to you AllAirports which returns an entry for a given (hopefully unique!) airport_code:
extension AllAirports {
static func airport(byCode airportCode: String, in context: NSManagedObjectContext) -> AllAirports? {
let request: NSFetchRequest<AllAirports> = fetchRequest()
request.predicate = NSPredicate(format: "airport_code == %#", airportCode)
if let result = try? context.fetch(request) {
return result.first
}
return nil
}
}
This could then be used in your code as follows:
func saveData(context: NSManagedObjectContext){
xArray.forEach { (data) in
let entity = Airports(context: context)
entity.airport_code = data.airport_code
entity.access_points = data.access_points
entity.image_url = data.image_url
if let airport = AllAirports.airport(byCode: data.airport_code, in: context) {
entity.lat = airport.latitude
entity.lon = airport.longitude
}
}

Swift 2 to swift 3 conversion Midi Input

I'm hoping someone may be able to help i'm using Xcode 8 and swift 3
I have a playground file Xcode 7 swift 2 that involves a Midi callback for Midi Input everything works fine in 7
I tried a conversion to 8 and it brought up errors regarding memory and a few name changes mostly of what i believe to be non serious i also redefined the infinite loop using PlaygroundSupport
However the error i cannot get over involves MyMIDIReadProc at
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
The error says
Cannot convert value of type '(pktList: UnsafePointer, readProcRefCon: UnsafeMutablePointer, srcConnRefCon: UnsafeMutablePointer) -> Void' to expected argument type 'MIDIReadProc' (aka '#convention(c) (UnsafePointer, Optional>, Optional>) -> ()')
My understanding is that it needs a #convention(c) wrapper of some description inserted. I think i'm on the right track because you can wrap a function but my knowledge of where to put it has run out. Again i was hoping some one might be able to advise
Thanks for reading
apologies for any bad language as i'm self taught
Here is the original Xcode 7 code
import Cocoa
import CoreMIDI
import XCPlayground
func getDisplayName(obj: MIDIObjectRef) -> String
{
var param: Unmanaged<CFString>?
var name: String = "Error";
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
if err == OSStatus(noErr)
{
name = param!.takeRetainedValue() as String
}
return name;
}
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
let packetList:MIDIPacketList = pktList.memory;
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(COpaquePointer(srcConnRefCon)).memory;
print("MIDI Received From Source: \(getDisplayName(srcRef))");
var packet:MIDIPacket = packetList.packet;
for _ in 1...packetList.numPackets
{
let bytes = Mirror(reflecting: packet.data).children;
var dumpStr = "";
// bytes mirror contains all the zero values in the ridiulous packet data tuple
// so use the packet length to iterate.
var i = packet.length;
for (_, attr) in bytes.enumerate()
{
dumpStr += String(format:"$%02X ", attr.value as! UInt8);
--i;
if (i <= 0)
{
break;
}
}
print(dumpStr)
packet = MIDIPacketNext(&packet).memory;
}
}
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
// Keep playground running
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true;
And here is the Xcode 8 code converted
var str = "Hello, playground"
import Cocoa
import CoreMIDI
import XCPlayground
import PlaygroundSupport
func getDisplayName(obj: MIDIObjectRef) -> String
{
var param: Unmanaged<CFString>?
var name: String = "Error";
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
if err == OSStatus(noErr)
{
name = param!.takeRetainedValue() as String
}
return name;
}
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
let packetList:MIDIPacketList = pktList.pointee;
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(OpaquePointer(srcConnRefCon)).pointee;
print("MIDI Received From Source: \(getDisplayName(obj: srcRef))");
var packet:MIDIPacket = packetList.packet;
for _ in 1...packetList.numPackets
{
let bytes = Mirror(reflecting: packet.data).children;
var dumpStr = "";
var i = packet.length;
for (_, attr) in bytes.enumerated()
{
dumpStr += String(format:"$%02X ", attr.value as! UInt8);
i -= 1;
if (i <= 0)
{
break;
}
}
print(dumpStr)
packet = MIDIPacketNext(&packet).pointee;
}
}
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
PlaygroundPage.current.needsIndefiniteExecution = true
Pointer types are drastically changed in Swift 3. Many C-based APIs' signatures are changed accordingly.
Following those changes manually would be painful. You can make Swift work for you, with a little modification.
Try changing the function header:
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
to a closure declaration:
let MyMIDIReadProc: MIDIReadProc = {pktList, readProcRefCon, srcConnRefCon in
Swift infers argument types perfectly in this style.
You may need to fix pointer type conversion:
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(OpaquePointer(srcConnRefCon)).pointee;
to something like this:
//I'm not sure using `!` is safe here...
let srcRef: MIDIEndpointRef = UnsafeMutablePointer(srcConnRefCon!).pointee
(By the way, the equivalent part in your Xcode 7 code is a little bit redundant. You have no need to use intermediate COpaquePointer there.)
In Swift 3, pointers cannot be nil, and nullable pointers are represented with Optionals. You may need many other fixes to work with C-based APIs in Swift 3.
OOPer is pointing (ahem) you in the right direction. Here is a blog post on using Swift 3 Core MIDI along with a working github repo.
Assuming that you're working with CoreMIDI 1.3 or later, you may have more luck using MIDIInputPortCreateWithBlock instead of MIDIInputPortCreate.
This method takes a Swift block as a parameter instead of requiring an #convention(c) function reference, making it more amenable to use within methods belonging to Swift classes, e.g.:
public func midiReadBlock(ptr: UnsafePointer<MIDIPacketList>, _: UnsafeMutableRawPointer?) -> Void {
let list: MIDIPacketList = ptr.pointee
...
}
You may also find these two extensions useful.
This one (derived from here) allows you to iterate directly over a MIDIPacketList using for pkt in list:
extension MIDIPacketList: Sequence {
public func makeIterator() -> AnyIterator<MIDIPacket> {
var iterator: MIDIPacket?
var nextIndex: UInt32 = 0
return AnyIterator {
nextIndex += 1
if nextIndex > self.numPackets { return nil }
if iterator != nil {
iterator = withUnsafePointer(to: &iterator!) { MIDIPacketNext($0).pointee }
} else {
iterator = self.packet;
}
return iterator
}
}
}
and this one adds a method to a MIDIPacket to extract the contents as a [UInt8] instead of having to use the really broken tuple syntax:
extension MIDIPacket {
public var asArray: [UInt8] {
let mirror = Mirror(reflecting: self.data)
let length = Int(self.length)
var result = [UInt8]()
result.reserveCapacity(length)
for (n, child) in mirror.children.enumerated() {
if n == length {
break
}
result.append(child.value as! UInt8)
}
return result
}
}

Swift 3.0 NSFetchRequest error [duplicate]

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()
}

Realm search Database with Predicate in Swift 2

I write an (Mac)App in Swift2 which should search a Realm database of Teachers with a sepcific subject. The Definition for the Teacher Object in realm looks like this:
class Teacher: Object {
var subjects = List<Subject>()
}
This class is very complex so I deleted some lines...
Here's the function that should filter the Database for Teachers with Specific subjects and give only the Teacher names back (as String Array: [String]):
func getAllTeacherNamesForSubject(subject: String) -> [String] {
// Open Database
let realm = openRealmDatabase()
// Or open it this way:
// RLMRealm.setDefaultRealmPath("/Users/name/Data.realm")
// var realm: Realm!
// realm = try! Realm()
// filter the Database with Predicate
let objects = realm.objects(Teacher).filter("!!! Need Help !!!", subject)
// How can I filter the Database? I want to get all the Teachers for that subject
// Make an Array of Objects
let objectsArray = objects.toArray(Teacher) as [Teacher]
// Return
return ???
}
// This is the toArray extension.
// You may need it to use the snippet above
extension Results {
func toArray<T>(ofType: T.Type) -> [T] {
var array = [T]()
for var i = 0; i < count; i++ {
if let result = self[i] as? T {
array.append(result)
}
}
return array
}
}
So the problem is that I don't know how to filter the Database.
Can someone help me please?
To filter objects that have relations with specific values, or in your case, teachers that have subjects with a specific name, you could use this predicate for a case-insensitive search:
NSPredicate(format: "ANY subjects.name ==[c] %#", subjectName)
and just plug that into the filter function. Since you want to return only the teacher names, you won't need to create any extensions as suggested, but rather use the native swift map method:
func getAllTeacherNamesForSubject(subjectName: String) -> [String] {
let realm = openRealmDatabase
let predicate = NSPredicate(format: "ANY subjects.name ==[c] %#", subjectName)
return realm.objects(Teacher).filter(predicate).map { $0.name }
}
For reference, you can find a great cheatsheet on Realm's website describing the complete list of supported predicate syntax.

Swift: filter protocol array by comparing types

(first post)
usually im able to find answers here or elsewhere but no luck this time =(
Question: in Swift, how do you filter an array that is of a protocol type by an implementing type supplied as a function parameter?
protocol Aprotocol {
var number:Int { get set }
}
class Aclass: Aprotocol {
var number = 1
}
class AnotherClass: Aprotocol {
var number = 1
}
var array:[Aprotocol] = [ Aclass(), AnotherClass(), Aclass() ]
func foo (parameter:Aprotocol) -> Int {
return array.filter({ /* p in p.self == parameter.self */ }).count
}
var bar:Aprotocol = // Aclass() or AnotherClass()
var result:Int = foo(bar) // should return 2 or 1, depending on bar type
maybe this is not the right approach at all?
thanks!
Here is what I think you want:
return array.filter { (element: Aprotocol) -> Bool in
element.dynamicType == parameter.dynamicType
}.count
But I recommend this, which does the same, but without the useless instance of Aclass() which is passed in the answer on the top. Also this way is faster:
func foo <T: Aprotocol>(type: T.Type) -> Int {
return array.filter { (element: Aprotocol) -> Bool in
element.dynamicType == type
}.count
}
var result:Int = foo(Aclass)
The dynamicType will return the Type of an instance
Very easy:
return array.filter({ parameter.number == $0.number }).count
Kametrixoms solution works (if you use "is T" instead of "== type") but in my case, since i didnt know which implementing class was going to call it, had to go with this solution:
protocol Aprotocol: AnyObject {
var number:Int { get set }
}
class func foo(parameter: AnyObject) -> Int {
return array.filter ({ (element: Aprotocol) -> Bool in
object_getClassName(element) == object_getClassName(parameter)
}).count
}

Resources