I am making a NSDictionary to hold data for a tableview. The tableview is like a contacts list, with contacts separated by first name and sorted alphabetically, so I am making a dictionary to hold these contacts. The dictionary is defined as [String: [User]]() where User is my own object.
However, when I check to see if there are any entries for the first letter in a user's first name, I get the compile error: Could not find member init on my code.
Here is my code:
var users = CurrentAccount.friends
var organizedUsers = [String: [User]]()
func organizeFriendsByFirstName() {
for user in users {
if let xc = organizedUsers["\(user.firstName!.substringToIndex(1))"] {
} else {
organizedUsers["\(user.firstName!.substringToIndex(1))"] = [user]
}
}
}
You error not have anything to do with your NSDictionary, sometimes the Swift compiler it's not accurate with its compile errors, see this nice article Swift Compiler Diagnostics.
If you put outside the line user.firstName!.substringToIndex(1) this throws the compile error :
Cannot invoke 'substringToIndex' with an argument list of type '(Int)'.
And it is the cause of your error, you can use the advance function to make indexes in an String, change your function to the following:
func organizeFriendsByFirstName() {
for user in users {
if let xc = organizedUsers["\(user.firstName!.substringToIndex(advance(user.firstName!.startIndex, 1)))"] {
} else {
organizedUsers["\(user.firstName!.substringToIndex(advance(user.firstName!.startIndex, 1)))"] = [user]
}
}
}
I hope this help you.
Related
OK, first, I know that there is no such thing as AnyRealmObject.
But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.
Currently, I have something like this:
enter code here
class Family: Object {
var pets: List<Pet>
}
class Pet: Object {
var dog: Dog?
var cat: Cat?
var rabbit: Rabbit?
}
Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.
What I really want to do is this:
class Family: Object {
var pets: List<Object>
}
Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>
The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.
Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:
class ObjectReference: Object {
var className: String
var primaryKeyValue: String
public init(with object: Object) {
className = ???
primaryKeyValue = ???
}
public func object() -> Object? {
guard let realm = realm else { return nil }
var type = ???
var primaryKey: AnyObject = ???
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
}
The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.
(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)
Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:
public func object() -> Object? {
let applicationName = // (application name goes here)
guard let realm = realm else { return nil }
guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
print("Error: \(className) isn't the name of a Realm class.")
return nil
}
var primaryKey: String = primaryKeyValue
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)
In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).
This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.
While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...
Let's define some Realm objects
class BookClass: Object {
#objc dynamic var author = ""
}
class CardClass: Object {
#objc dynamic var team = ""
}
class MugClass: Object {
#objc dynamic var liters = ""
}
and then a base realm object called Inventory Item Class that will represent them
class InvItemClass: Object {
#objc dynamic var name = ""
#objc dynamic var image = ""
#objc dynamic var itemType = ""
#objc dynamic var book: BookClass?
#objc dynamic var mug: MugClass?
#objc dynamic var card: CardClass?
}
then assume we want to store some books along with our mugs and cards (from the comments)
let book2001 = BookClass()
book2001.author = "Clarke"
let bookIRobot = BookClass()
bookIRobot.author = "Asimov"
let item0 = InvItemClass()
item0.name = "2001: A Space Odyssey"
item0.image = "Pic of Hal"
item0.itemType = "Book"
item0.book = book2001
let item1 = InvItemClass()
item1.name = "I, Robot"
item1.image = "Robot image"
item1.itemType = "Book"
item1.book = bookIRobot
do {
let realm = try Realm()
try! realm.write {
realm.add(item0)
realm.add(item1)
}
} catch let error as NSError {
print(error.localizedDescription)
}
From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.
do {
let realm = try Realm()
let items = realm.objects(InvItemClass.self)
for item in items {
switch item.itemType {
case "Book":
let book = item.book
print(book?.author as! String)
case "Mug":
return
default:
return
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.
I'm new in Realm and I tried to add an Array as I did with strings and I ended up with some errors. So after a little search I found out a solution:
class Sensors : Object {
dynamic var name = ""
dynamic var message = ""
var topic: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["topic"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
This is working very good, now I want to add another array inside this class.
If someone knows any other ways to add arrays with realm please share it.
Thanks in advance
As a general rule it's way more efficient to use the one-to-many relationships provided by Realm instead of trying to emulate them by using arrays (Realm's collections are lazy, the objects contained are instantiated only when needed as opposed to plain Swift arrays).
In your case, if I understand correctly what you're trying to do, you want to add [RealmString] Swift arrays to the _backingNickNames list.
Why not use the append(objectsIn:) method of Realm's List class (see here), like this:
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person?
}
// Person model
class Person: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
let dogs = List<Dog>()
}
let jim = Person()
let dog1 = Dog()
let dog2 = Dog()
// here is where the magic happens
jim.dogs.append(objectsIn: [dog1, dog2])
If you want to do the opposite (convert from a List to an Array) just do :
let dogsArray = Array(jim.dogs)
• • • • • • • •
Back to your own posted solution, you could easily refactor the model to accommodate this. Each Sensor object could have several Topic and several Message objects attached.
Just ditch the message and topic computed properties and rename topicV and messageV to topics and messages respectively. Also rename RealmString to Topic and RealmString1 to Message.
Now, you could easily iterate through the, say, topics attached to a sensor like this :
for topic in sensor1.topics { ... }
Or if you want to attach a message to a sensor you could do something like this (don't forget to properly add the newly created object to the DB first):
let message1 = Message()
message1.stringValue = "Some text"
sensor2.messages.append(message1)
So, no need to use intermediary Swift Arrays.
After testing I managed to add another array like that:
class Sensors : Object {
dynamic var type = ""
dynamic var name = ""
dynamic var badge = 0
var topic: [String] {
get {
return topicV.map { $0.stringValue }
}
set {
topicV.removeAll()
topicV.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
var message: [String] {
get {
return messageV.map { $0.stringValue1 }
}
set {
messageV.removeAll()
messageV.append(objectsIn: newValue.map({ RealmString1(value: [$0]) }))
}
}
let topicV = List<RealmString>()
let messageV = List<RealmString1>()
override static func ignoredProperties() -> [String] {
return ["topic", "message"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
class RealmString1: Object {
dynamic var stringValue1 = ""
}
What bogdanf has said, and the way you've implemented it are both correct.
Basic value types aside, Realm can only store references to singular Realm Object objects, as well as arrays of Objects using the List type. As such, if you want to save an array of types, it's necessary to encapsulate any basic types you want to save (like a String here) in a convenience Realm Object.
Like bogdanf said, it's not recommended to convert Realm Lists to standard Swift arrays and back again, since you lose the advantages of Realm's lazy-loading features (which can cause both performance and memory issues), but memory issues can at least be mitigated by enclosing the code copying data out of Realm in an #autoreleasepool block.
class MyObject: Object {
dynamic var childObject: MyObject?
let objectList = List<MyObject>()
}
So in review, it's best practice to work directly with Realm List objects whenever possible, and to use #autoreleasepool any time you do actually want to loop through every child object in a Realm. :)
I'm facing a very common issue with Parse and iOS.
I have a class POST with the following structure:
text(String)
Image (PFFile)
LikesUsers (Array of String)
LikesCount (Int)
From (Pointer to User who posted it)
and if the user (already logged in) likes a post. I'm just incrementing the likes and the add Objectid of the user to the array
For example : User-2 likes User-1's Post.
PostObject.incrementKey("Likes")
PostObject.addObject((PFUser.currentUser()?.objectId)!, forKey: "LikesUsers")
PostObject.saveEventually()
The issue is here. I can't save a the PostObject as long as it has a pointer to another user (than the logged in) I'm getting the error :
User cannot be saved unless they have been authenticated via logIn or
signUp
So how to prevent from saving the ParseObject Children ("From")
I don't want to use a CloudCode, I want to keep it simple and to Use SaveEventually for a better user experience.
From the parse.com forums:
When you save an object with a pointer, Parse looks at the entirety of the object and saves the fields that are "dirty" (changed since last save). It also inspects all the objects being pointed to by pointers. If your object has a pointer to PFUser and the instance of the PFUser is "dirty", then Parse will save the object and then attempt to save the instance of the PFUser as well. My hypothesis is that somewhere in your code you are setting a value on a user and then not saving the user. This, I suspect, is the source of your error. Go through your code and look for anywhere you set a PFUser instance and make sure there's a corresponding save. If you can't find it at first glance, be sure to check all blocks and ensure that you are not asynchronously dirtying a user and subsequently trying to save it.
Are you trying to make any changes to the user that the post was made by?
Short answer:
Try to set from field as:
let author = PostObject["from"] as! PFObject
PostObject["from"] = PFObject(withoutDataWithClassName: "_User",
objectId: author.objectId)
This is not tested.
But I suggest you to change the architecture
I was facing the same problem and finally decided to move likes field to User class instead and make it Relation<Post> type. Parse documentation says that Relation type is better for keeping many objects. Since LikesUsers is an array of objects, the performance may drop significantly if a post will get many likes: the app will download all the users liked this post. See this for more info: https://parse.com/docs/ios/guide#objects-relational-data
Here is how my class structure looks like (simplified):
User:
postLikes (Relation)
Post:
author (PFObject)
likesCount (Int)
I also moved like/dislike logic to CloudCode function:
/// The user liked or disliked the post.
Parse.Cloud.define("likeDislikePost", function(request, response) {
var userProfileId = request.params.userProfileId
var postId = request.params.postId
// Shows whether the user liked or disliked the post. Bool value
var like = request.params.like
var query = new Parse.Query("_User");
query.equalTo("objectId", userProfileId);
query.first({
success: function(userProfile) {
if (userProfile) {
var postQuery = new Parse.Query("Post");
postQuery.equalTo("objectId", postId);
postQuery.first({
success: function(post) {
if (post) {
var relation = userProfile.relation("postLikes");
if (like) {
relation.add(post);
post.increment("likesCount");
}
else {
relation.remove(post)
post.increment("likesCount", -1);
}
post.save();
userProfile.save();
console.log("The user with user profile id " + userProfileId + " " + (like ? "liked" : "disliked") + " the post with id " + postId);
response.success();
}
else {
response.error("Unable to find a post with such ID");
}
},
error: function() {
response.error("Unable to like the post");
}
});
}
else {
response.error("Unable to find a user profile with such ID");
}
},
error: function() {
response.error("Unable to like the post");
}
});
});
Works just fine.
I created some project from scratch for you. I think you miss some thing when you are creating your PFClass. I don't know. Whatever look my codes,
This is the ViewController and it contains like Button Action;
import UIKit
class ViewController: UIViewController {
private let test = Test() // PFObject SubClass
private let post = Post() // PFObject SubClass
#IBAction func likeButton() {
self.test.fromUser = PFUser.currentUser() // Current User
self.test.likedUsers.append(Post().user // In there your post should be a Subclass of PFObject too like your this Test class and it should be contains Owner property. So you can grab the Post owner PFUser or User Object ID.)
self.test.saveEventually() {
(let success, error) in
if error == nil {
// Success
} else {
print(error?.localizedDescription)
}
}
}
}
In your AppDelegate init your ParseSDK and Register your new Parse subclasses.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
Parse.setApplicationId("YOUR_API_KEY", clientKey: "YOUR_CLIENT_KEY")
Test.registerSubclass()
Post.registerSubclass()
return true
}
Test Subclass;
import Foundation
import Parse
class Test: PFObject, PFSubclassing {
// Your Properties
#NSManaged var likedUsers: [PFUser]
#NSManaged var fromUser: PFUser
static func parseClassName() -> String {
return "Test" // Your Class Name
}
}
Post Subclass;
class Post: PFObject, PFSubclassing {
// Your Properties
#NSManaged var owner: PFUser
#NSManaged var likeCount: NSNumber
static func parseClassName() -> String {
return "Post" // Your Class Name
}
}
In my case it works fine. If you get an error please inform me.
Have a nice coding.
I'm using Swift 2 to make a function that creates a bunch of objects from a class I wrote.
Now, the problem is that when I'm adding a new object I have no problem, I checked multiple times both from the function and from the init() function that it stores the data. (Printing self.myVariable from the init() function works).
However, when later I go to retrive my variable for example like
if let dog: Animal? = Animal.init(id : "12345"){
print(dog?.legs)}
That prints nil, even though I checked in the Animal class and it successfully initialized the Animal object "dog".
If anybody has any idea why this is happening that would be really useful and appreciated.
Here's the link to the actual code, the Animal class was just an example but this is probably more useful : Class & Function
This is in response to your class code here.
Inside of your init function you are never setting the class variable, id. You could set it as follows:
query.getObjectInBackgroundWithId(id as String) { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
}
else {
if let atm = object {
print("ATM found")
self.bank = atm["bank"] as? NSString
self.type = atm["type"] as? NSNumber
self.location = atm["location"] as? PFGeoPoint
self.description = atm["description"] as? NSString
// Set id to whatever you'd like here
self.id = atm.objectId!
}
}
}
I'm not familiar with PFQuery but since it's using a completion handler, it is responding asynchronously to the call to getObjectInBackground. That means that when you get your object from init(id:...) it does not yet have a value in the bank field. So result?.bank prints nil because bank is nil, not because result is nil.
Once the completion handler is executed, bank will have its value (if no error occurred) but it would be unlikely that this would happen in the short time between init() and your let result : ATM? = ... condition.
I'm trying to create a custom class for PFObject (Subclassing), which seems to work fine, but when I try to convert/use the custom class object, as a regular PFObject, It messes up. Here's what I'm trying to do.
First I have created the Custom Class named NewPFObject for testing reasons.
NOTICE: I AM calling NewPFObject.registerSubclass() in the AppDelegate before setting the Application Id.
class NewPFObject: PFObject, PFSubclassing {
static func parseClassName() -> String {
return "NewPFObject"
}
override init(className:String) {
super.init(className: className)
}
}
Then I have this method that's using it to make Async calls more easy and fluid:
func GetAsyncObjects(query:STARCQuery, doNext: (dataObjects:[NewPFObject]) -> ()) {
query.findObjectsInBackgroundWithBlock { (newObjects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
doNext(dataObjects: newObjects as! [NewPFObject])
}
}
}
And finally, I have the use-case, where the error happens.
let query:PFQuery = PFQuery.init(className: "MyCustomClassInParse")
GetAsyncObjects(query) { (dataObjects) -> () in
if(dataObjects.count > 0) {
for customObject in dataObjects {
//Do something with customObject data
}
}
}
The error at the use-case is the same as the title:
fatal errror: NSArray element failed to match the Swift Array Element type
And It happens on the final block of code, on the line where I use the dataObjects Array in the for loop.
When trying to cast it multiple times makes XCode say that It's redundant to do so, and It doesn't make a difference when actually running the code. Same error.
I've literally tried everything, and every post about PFSubclassing and this error on Stackoverflow, can't seem to find the solution, so I hope someone Is willing to help me out!
Thanks!
The value you return from parseClassName must match the class name that is defined in Parse.com, so in your case, parseClassName would need to return MyCustomClassInParse
This enables the Parse framework to match the PFSubclassing class to the Parse.com class and return the appropriate object. If there is no match then you will just get plain-old PFObject instances, which is why you get a runtime error when you try to downcast.