I have a custom NSMutableArray and i want to sort it; I've looked over the internet but nothing solve my problem. what I have is the following Locations.swift
import UIKit
class Location: NSObject {
var id:String = String()
var name:String = String()
var distance:Float = Float()
}
then I create a Mutable Array from this class in my ViewController.swift
class CustomViewController: UIViewController{
var locations: NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
locations = NSMutableArray()
locations = SqLite.getInstnace().getLocations()//get values from DB، objects of Location
}
}
how I can sort locations by the distance value??
I would make location an array of Location objects, and then sorting becomes easy with sortInPlace.
class CustomViewController: UIViewController{
var locations = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = SqLite.getInstnace().getLocations().flatMap { $0 as? Location }
}
}
Now you can sort locations like this:
locations.sortInPlace { $0.distance < $1.distance }
You could always use the built in sort function:
let locations = SqLite.getInstnace().getLocations().sort({
return ($0 as! Location).distance < ($1 as! Location).distance
})
if SqLite.getInstnace().getLocations() return an array as [Location] and not a NSArray then you could just do this since it will already know the elements types:
let locations = SqLite.getInstnace().getLocations().sort({
return $0.distance < $1.distance
})
This should return a sorted array in ascending order if you want descending just use > instead.
Also, there is really no need to use NSMutableArrays in Swift. Just use [Location].
Edit:
NSMutableArray can be used in swift but it considered objective-c like where as using [OBJECT_TYPE] is conventional for swift.
Take a look at the Swift 2 book array segment. None of the examples use NSArray or NSMutable array.
By declaring something with var in swift makes it mutable where as let makes something immutable
Related
I have a UIViewController containing a UITableView that is populated via an array of custom class objects. These custom class objects have an array property. As you can see from my code below, the objects are equatable. When I segue to a second vC, the array of custom class objects (and obviously the array properties associated with each object) is passed over.
I have a function in the second vC that matches an object with one that is contained in the array. After matching, the property array of the object in the array that matched is updated. However, when I print what should be the updated property array, no change has been made. Below is a representation of my code:
class Object: Equatable {
var propertyArray: [String] = []
static func ==(lhs: object, rhs: object) -> Bool {
return lhs.someProperty == rhs.someProperty
}
}
class ArrayOfObjects {
var array: [Object] = []
}
class vC1: UIViewController, UITableViewDelegate, UITableViewDataSource {
var objectArray1 = ArrayOfObjects()
override viewDidLoad() {
//populate objectArray1.array
}
prepareForSegue(segue: UIStoryboardSegue) {
segue.destination.objectArray2 = objectArray1 //segue to vC2
}
}
class vC2: UIViewController {
var objectArray2 = ArrayOfObjects()
var someOtherObject = Object() //object that has same properties as an object in objectArray2
func someFunc() {
let indexOfMatchingObject = objectArray2.array.index(of: someOtherObject)
let matchingObject = objectArray2.array[indexOfSomeOtherObject]
matchingObject.propertyArray.append("TestString")
print("\(matchingObejct.propertyArray)") //prints []
}
}
Why doesn't it print ["TestString"]? The same goes for when I remove one of the values from the array, the update doesnt occur.
If you are wondering why I am doing this, it's because the objects are modified in the second vC and the UI of the tableView cells in the first vC are dependent upon the properties of the objects. Hence why my data is represented as a class (reference type).
Update your someFunc():
func someFunc() {
//try to find the same object
let indexOfMatchingObject = objectArray2.array.index(of: someOtherObject)
//obtain this common objec from array
let matchingObject = objectArray2.array[indexOfSomeOtherObject]
//update propertyArray from matchingObject
matchingObject.propertyArray.append("TestString")
}
Also update your first VC table view in viewWillApear.
I realised that the answer to my question is nothing to do with the class I had created or the segue, but is in actual fact to do with the state of the array. You cannot append to an array that has not been initialised. In order to append to it, you must first initialise it, then you may append to it. Playground code has been provided below to demonstrate this in the context of the question above:
//Create object class
class Object: Equatable {
var propertyArray: [String]?
var id: Int = Int()
static func ==(lhs: Object, rhs: Object) -> Bool {
return lhs.id == rhs.id
}
}
//Create class that will hold array of objects
class ArrayOfObjects {
var array: [Object] = []
}
//initialise class that will hold array of objects
var array = ArrayOfObjects()
//set the initial values for the array
func setArray() {
let object1 = Object()
object1.id = 1
object1.propertyArray = ["hello"]
let object2a = Object()
object2a.id = 2
object2a.propertyArray = ["bye"]
array.array = [object1, object2a]
}
setArray()
//Create new object that will be used to match with one already in the array
let object2b = Object()
object2b.id = 2
object2b.propertyArray = ["bye"]
//Find if the new object exists in the array
let index = array.array.index(of: object2b)
let matchingObject = array.array[index!]
matchingObject.propertyArray?.append("welcome")
//We were able to append to the matchingObject (object2a) because the property array had been initialised
print(matchingObject.propertyArray) //prints ["bye", "welcome"]
//Create new object with uninitialised propertyArray
let object3a = Object()
object3a.id = 4
//Append this new object to the array
array.array.append(object3a)
//Create another new object that will be used to match with object3a
var object3b = Object()
object3b.id = 4
//Find if object3b can be matched in the array
let index2 = array.array.index(of: object3b)
let matchingObject2 = array.array[index2!]
matchingObject2.propertyArray?.append("hello")
//A match was found for object3b, but the array had not been initialised and so we couldn't append to it
print(matchingObject2.propertyArray) //prints nil
//Initialise the array
matchingObject2.propertyArray = []
matchingObject2.propertyArray?.append("goodbye")
//We can now append to it as it has been initialised
print(matchingObject2.propertyArray) //prints ["goodbye"]
I just start to learn Realm data persistence, I start it from a iOS test project.
The realm object is declared like this:
class AchievementRecord: Object {
dynamic var dateID:String = "1111-00-00"
dynamic var date:String = "0000-00-00"
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "dateID"
}
}
I initialise the object in View Controller's viewDidLoad() method as this:
class AchievementRecord: Object {
dynamic var dateID:String = "1111-00-00"
dynamic var date:String = "0000-00-00"
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "dateID"
}
}
then I declare another function to obtain the save data as:
let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'")
print(appleOn_05)
In the console, Xcode says:
Because I need to retrieve the apple's number, which is 22 in the console. How can I retrieve the apple's number to demo it on the screen, how can I do it? Thanks in advance.
Results works like native Swift collections in many ways. If you are fetching a single object, you can just access it with Results.first let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'").first
Subclasses of Object work like any other native class instance in Swift, so you can access their properties using the dot syntax.
let apple = appleOn_05.apple
Combining the two:
if let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'").first {
let apple = appleOn_05.apple
}
I have an array of EKReminder, like so:
var currentReminders: [EKReminder]? = ....
I want to cast this array to an array of subclasses of EKReminder. Let's say this is the subclass:
import Foundation
import EventKit
class NNReminder: EKReminder {
var additionalNotes: String?
}
How would I cast currentReminders to [NNReminder]? I tried several ways but they all failed.
Provided you are sure that all members of currentReminders are, in fact, NNReminders, you can cast them all like this:
currentReminders = currentReminders.map { $0 as! NNReminder }
Edit: if only some of the reminders are of type NNReminder, or you're not sure of the array's contents, you can use flatMap to remove the nil values:
currentReminders = currentReminders.flatMap { $0 as? NNReminder }
If you are asking how to transform a bunch of objects that were initialized as EKReminder, you should write a custom init in NNReminder that takes an EKReminder, and use this init in the above map method.
I was tired of having cryptic .map clauses all over my code so i wrote a small extension to make things neat:
extension Array{
func cast<T>(type:T.Type? = nil) -> [T]{
return self.map { $0 as! T }
}
}
Example:
class A:B{
var value:String = "default"
init(_ value:String){
self.value = value
}
}
class B{
var someNum:CGFloat = 1
init(){
}
}
var arr1:Array<A> = [A("a"),A("b"),A("c")]
let arr2:Array<B> = arr1.cast(B.self)//neat!
let arr3:Array<B> = arr1.cast()//epic!
NOTE:
the cast method supports protocols as well
I'm having trouble getting my NSManagedObject to work.
In one file I have:
import UIKit
import CoreData
#objc(Location)
class Location: NSManagedObject {
#NSManaged var title:String
#NSManaged var lat:NSDecimalNumber
#NSManaged var lon:NSDecimalNumber
#NSManaged var course:NSDecimalNumber
#NSManaged var alt:NSDecimalNumber
}
In a TableView class I have:
...
var locations:NSArray = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = self.getAllLocations()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var logCell:TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("loggedCell") as TableViewCell
logCell.loggedTitle.text = locations[indexPath.row].title
if let lat = locations[indexPath.row].lat {
println(lat)
}
return logCell
}
func getAllLocations() -> [Location] {
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Locations")
request.returnsObjectsAsFaults = false
if let results = context.executeFetchRequest(request, error: nil) {
return results as [Location]
} else {
// Failed, return empty error. Alternatively, report error.
return []
}
}
...
I am able to retrieve the values from CoreData. If I println( locations[indexPath.row] ) it's all there. However in func tableView I am only able to get the title. lat, lon, everything but title errors out with 'AnyObject' does not have a member named 'lat', as in the lines:
if let lat = locations[indexPath.row].lat {
println(lat)
}
I'm sure it's something basic as I am new to Swift and iOS development. I would appreciate a point in the right direction please.
It's partly about Swift's strict typing and partly about the interplay between Swift types and Cocoa (Objective-C) types.
The problem here is that you have typed locations as an NSArray. That's a Cocoa collection, not a Swift collection. Swift doesn't know what kind of thing is inside an NSArray.
You may know (or believe) that what's at locations[indexPath.row] is a Location, therefore, but Swift doesn't know that. You have to tell it, by casting (with as).
Alternatively, if there is no reason not to do so, type locations as a Swift array of Location, a [Location]. Now Swift knows what's in it. I should also point out that as things stand, your initializer is pointless:
var locations:NSArray = [Location]()
You start by making a Swift array of Location, yes, but then by typing locations as an NSArray and assigning into it, you throw away all knowledge that this thing is supposed to contain only Location objects.
I'm trying to do the following.
protocol Vehicle {
}
class Car : Vehicle {
}
class VehicleContainer<V: Vehicle> {
}
let carContainer = VehicleContainer<Car>()
let vehicleContainer = carContainer as VehicleContainer<Vehicle>
But I get the compile error on the last line:
'Car' is not identical to 'Vehicle'
Is there any workaround for this?
Also I believe this type of casting should be possible because I can do it with Arrays which are built on generics. The following works:
let carArray = Array<Car>()
let vehicleArray = carArray as Array<Vehicle>
Expanding your array example quickly in playground works as intended
protocol Vehicle {
}
class Car : Vehicle {
}
class Boat: Vehicle {
}
var carArray = [Car]()
var vehicleArray : [Vehicle] = carArray as [Vehicle]
vehicleArray.append(Car()) // [__lldb_expr_183.Car]
vehicleArray.append(Boat()) // [__lldb_expr_183.Car, __lldb_expr_183.Boat]
Haven't done too much work with swift generics but looking quickly at the swift docs
struct Stack<T: Vehicle> {
var items = [Vehicle]()
mutating func push(item: Vehicle) {
items.append(item)
}
mutating func pop() -> Vehicle {
return items.removeLast()
}
}
and using vehicles instead of the generic type T
var vehicleStack = Stack<Vehicle>()
vehicleStack.push(Car())
vehicleStack.push(Boat())
var aVehicle = vehicleStack.pop()
appears to compile aok in an app (playground has some issues handling it though)