How to prevent Parse from saving PFObject children's? - ios

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.

Related

Realm - list of objects in an object (Swift 3)

I have a Models class defined like this:
class BaseModel: Object {
var data: JSON = JSON.null
convenience init(_ data: JSON) {
self.init()
self.data = data
}
override static func ignoredProperties() -> [String] {
return ["data"]
}
}
class RecipeModel: BaseModel {
dynamic var title: String {
get { return data["fields"]["title"].stringValue }
set { self.title = newValue }
}
... more vars ...
var ingredients: List<IngredientsModel> {
get {
let ingredients = List<IngredientsModel>()
for item in data["fields"]["ingredients"] {
ingredients.append(IngredientsModel(item.1))
}
return ingredients
}
set { self.ingredients = newValue }
}
}
class IngredientsModel: BaseModel {
dynamic var text: String {
get { return data["text"].stringValue }
set { self.text = newValue }
}
... more vars ...
}
And I would like to use it something like this:
Api.shared.fetchAllEntries().call(onSuccess: {response in
print(response.json)
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
for item in response.json["items"].arrayValue {
let recipe = RecipeModel(item)
try! realm.write {
realm.add(recipe)
}
}
}, onError: {
print("error")
})
So basically the idea is to just pass the whole JSON to the initial RecipeModel class, and it should parse it out and create the objects I need in the Realm database. It works quite well except for the nested list of IngredientsModel. They do not get added to the realm database.
What I see as a potential problem is that I call self.init() before I call self.data in the convenience init, but I do not see any way to work around this. Do you guys please know how I could achieve that also the IngredientsModel would have its contents set up properly and I would have a list of ingredients in the RecipeModel?
Your current implementation doesn't work, because you are not calling the getter/setter of ingredients in the init method of RecipeModel and hence the IngredientsModel instances are never persisted in Realm.
Moreover, using a computed property as a one-to-many relationship (Realm List) is a really bad idea, especially if you are parsing the results inside the getter for this property. Every time you call the getter of ingredients, you create new model objects instead of just accessing the existing ones that are already stored in Realm, but you are never deleting the old ones. If you were actually saving the IngredientsModel instances to Realm (which you don't do at the moment as mentioned above) you would see that your database is full of duplicate entries.
Your whole approach seems really suboptimal. You shouldn't store the unparsed data object in your model class and use computed properties to parse it. You should parse it when initializing your models and shouldn't store the unparsed data at all. You can use the ObjectMapper library for creating Realm objects straight away from the JSON response.

Appending Model into array in Swift

So I have created a model class where I am using Alamofire to get data from an API. I want to display that data in the table to view so I was thinking of appending that model into an array in the VC and call it in the custom cell.
But I want to append that model into the array only if a key value matches a particular string.
However when I am using a simple if statement in the VC its giving me a fatal error saying bad instruction.
Code
Calling the Model
var notificationModel: NotificationModel!
var notification = [NotificationModel]()
viewDidLoad
if notificationModel.type == "meeting" {
self.notification.append(notificationModel)
}
Model
Class NotificationModel
var _type: String!
var type: String {
if _type == nil {
_type = "Err"
}
return _type
}
func downloadData() {
...
}
}
Here you have force unwrap NotificationModel. And you are trying to access type of a nil value.
Please try with the following fix.
var notificationModel: NotificationModel!
var notification = [NotificationModel]()
if notificationModel?.type == "meeting" {
notification.append(notificationModel!)
}
Hope this resolves the issue.

Creating login/signup views in Xcode

I am making an app for iOS in Xcode.
My question is:
How do I make the app show the signup/login view when it is needed, but not show it when the user is already logged in?
Is it communicating with the database every time the app launches?
I am planning on using MySQL for creating a database with simple users (username, score, friends).
Is there a tutorial that will show me the steps for doing this?
I have no experience with databases.
Help is appreciated.
I'm gonna give you a comprehensive answer.
Don't use NSUserDefaults and don't store password it's a bad solution
NSUserDefaults data is not encrypted, it may cause security issue.
Let's create a structured user class instead
When the user logged in, you will need to make sure you have access to user data throughout the app so you can get the data on any screen when you need it.
To achieve this, we need to make a great structure to organize this properly. Remember that current user and another users are both "user" so we will use the same class.
Create a class and name it "EDUser" (you can choose other name if you want).
This class will contain a user information (either current user or other user).
More than that, this class will have capability to log the user in.
Here's a picture of what the class might look like:
class EDUser {
var firstName: String
var lastName: String?
var birthDate: NSDate?
init(firstName: String, lastName: String?, birthDate: NSDate?) {
self.firstName = firstName
self.lastName = lastName
self.birthDate = birthDate
}
}
// MARK: - Accessor
extension EDUser {
class var currentUser: EDUser? {
get {
return loadCurrentUserFromDisk()
}
set {
saveCurrentUserToDiskWithUser(newValue)
}
}
}
// MARK: - Log in and out
extension EDUser {
class func loginWithUsername(username: String,
andPassword password: String,
callback: (EDUser?, NSError) -> Void) {
// Access the web API
var parameters = [
"username": username,
"password": password
]
YourNetworkingLibrary.request(.POST,
"https://api.yourwebsite.com/login",
parameters: parameters).responseJSON {
response in
if response.statusCode == .Success {
let user = EDUser(firstName: response["firstName"],
lastName: response["lastName"],
birthDate: NSDate.dateFromString(response["birthDate"]))
currentUser = user
callback(currentUser, nil)
} else {
callback(nil, yourError)
}
}
}
class func logout() {
deleteCurrentUserFromDisk()
}
}
// MARK: - Data
extension EDUser {
class private func saveCurrentUserToDiskWithUser(user: EDUser) {
// In this process, you encode the user to file and store it
}
class private func loadCurrentUserFromDisk() -> EDUser? {
// In this process, you get the file and decode that to EDUser object
// This function will return nil if the file is not exist
}
class private func deleteCurrentUserFromDisk() {
// This will delete the current user file from disk
}
}
// MARK: - Helper
extension NSDate {
class func dateFromString(string: String) -> NSDate {
// convert string into NSDate
}
}
Use Case
Now with everything in place, we can use it like this
Non-blocking logging in process
EDUser.loginWithUsername(username: "edward#domain.com",
password: "1234") {
user, error in
if error == nil {
// Login succeeded
} else {
// Login failed
}
}
Logging out
EDUser.logout()
Check whether the user is logged in
if EDUser.currentUser != nil {
// The user is logged in
} else {
// No user logged in
// Show the login screen here
}
Get current user data on any screen
if let currentUser = EDUser.currentUser {
// do something with current user data
}
Store other user as object
let user = EDUser(firstName: "Edward",
lastName: "Anthony",
birthDate: NSDate())
You need to look at a bunch of tutorials, I recommend these guys:
https://www.raywenderlich.com
They have free tutorials in ObjC and Swift as well as video courses!
A REST API normally respond with a 401 (Unauthorized code) if there isn't a valid/logged user, so every time I get a 401 I show a login within a modal view.
So when you load the app, call a GET currentUser endpoint, if it returns a 401 the app will show the login, if not, the app will show the default rootViewController.
I like this way because if for any reason, your session is no longer valid, the app will show the login view.

Subclassing PFUser does not effect the data stored

I am using Swift.
This question talks about the Parse service.
I've read ways using both the #NSManaged and dynamic key-words, so I decided for my example to implement them both. The issue here is that in the User object of my data manager, I'm noticing that additional information is not being stored in the database. For my application I would like to store some additional information in the User table, such as their first and last name. Here's an example:
import Parse
public class User : PFUser {
#NSManaged public var firstName: String!
dynamic public var lastName: String!
override public class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
init(email: String, password: String) {
super.init();
self.username = email;
self.email = email;
self.password = password;
self.firstName = "MyFirstName";
self.lastName = "MyLastName";
}
}
Here's the code I'm using to initialize and send off the data:
#IBAction func register() {
let newUser = User(email: "myemail#provider.com", password: "my password");
newUser.signUpInBackgroundWithBlock { (success, error) -> Void in
if error == nil {
print("Success")
} else {
print(error);
}
}
}
Edit: Upon playing around in the dashboard it seems like fields that are added to the User table / document / whatever you want to call it in a schema less database are automatically removed. This would indicate that I would need to create another class (IE: UserInfo) to store all of the users information, such as first and last name. Is this correct? If so that seems a little odd, as it would increase the amount of requests to login to the application. (One for validation, one for retrieving information). Is this the correct way of handling this?
Adding properties to a subclass won't them automatically added to the PFUser instance. You will have to use the PFObject methods to set the properties and then save the PFUser object.
The Parse Documentation gives an example of setting an extra phone property on the PFUser instance before calling signup. This is how you can add the firstName and lastName properties to PFUser.

Xcode iOS: check if user is logged in and show different views if not

I'm coding an app where a logged in user has got a couple of extra functionalities than a non logged in user. Basically, I have more or less 5 tabs. When I launch the app, the user immediately gets the login page. He can decide to skip it. If he skips it, there'll only be 3 tabs for him. If he logs in successfully, there'll be 5.
I already have my login page made. I just don't know how I can store a session if the user is logged in correctly, and only display a certain number of tabs if the user isn't. I come from PHP, I've just started learning Objective-C, so I'm looking for the same thing as $_SESSION in PHP, more or less.
So: if user logs in, store session, and show all the tabs. If he doesn't, only show a limited number of tabs.
How should I approach this?
In terms of storing the session, I assume username and password is enough.
You could store the username as you wish in NSUserDefaults or CoreData if you are using it. Storing a password is best using the keychain. SSKeychain makes it easy to do this.
[SSKeychain setPassword:password forService:myAppName account:userName]
You could store the fact they are logged in in-memory, but on app relaunch check by:
NSString *password = [SSKeychain passwordForService:myAppName account:userName];
if (password != nil)
{
// Logged in
}
If the user logs out, easy as deleting the password from the keychain by
[SSKeychain deletePasswordForService:myAppName account:userName]
Session handling is done automatically when you use NSURLConnection, so you can store the users data in a Sesssion on the server.
What you might be looking for is called a Singleton design pattern (some people reject it, but it can be very handy). What you do is create one object that is available everywhere in your code. In this object you for example store a BOOL that indicates whether the user has logged in or not. For example:
(I didn't run this, just to get the idea)
Mananger myManager* = [Manager sharedManager];
if(myManager.loggedIn){
//Show 5 tabs
}else{
//Show 3 Tabs
}
This code can be used in every class so you can always access your user's data. Manager would be a seperate class in this case that provides singleton functionality. Check out how to make one here: http://www.johnwordsworth.com/2010/04/iphone-code-snippet-the-singleton-pattern/
I'm gonna give you a comprehensive answer.
Don't use NSUserDefaults as session it's a bad solution
NSUserDefaults data is not encrypted, it may cause security issue.
Let's create a structured user class instead
When the user logged in, you will need to make sure you have access to user data throughout the app so you can get the data on any screen when you need it.
To achieve this, we need to make a great structure to organize this properly. Remember that current user and another users are both "user" so we will use the same class.
Create a class and name it "EDUser" (you can choose other name if you want).
This class will contain a user information (either current user or other user).
More than that, this class will have capability to log the user in.
Here's a picture of what the class might look like:
class EDUser {
var firstName: String
var lastName: String?
var birthDate: NSDate?
init(firstName: String, lastName: String?, birthDate: NSDate?) {
self.firstName = firstName
self.lastName = lastName
self.birthDate = birthDate
}
}
// MARK: - Accessor
extension EDUser {
class var currentUser: EDUser? {
get {
return loadCurrentUserFromDisk()
}
set {
saveCurrentUserToDiskWithUser(newValue)
}
}
}
// MARK: - Log in and out
extension EDUser {
class func loginWithUsername(username: String,
andPassword password: String,
callback: (EDUser?, NSError) -> Void) {
// Access the web API
var parameters = [
"username": username,
"password": password
]
YourNetworkingLibrary.request(.POST,
"https://api.yourwebsite.com/login",
parameters: parameters).responseJSON {
response in
if response.statusCode == .Success {
let user = EDUser(firstName: response["firstName"],
lastName: response["lastName"],
birthDate: NSDate.dateFromString(response["birthDate"]))
currentUser = user
callback(currentUser, nil)
} else {
callback(nil, yourError)
}
}
}
class func logout() {
deleteCurrentUserFromDisk()
}
}
// MARK: - Data
extension EDUser {
class private func saveCurrentUserToDiskWithUser(user: EDUser) {
// In this process, you encode the user to file and store it
}
class private func loadCurrentUserFromDisk() -> EDUser? {
// In this process, you get the file and decode that to EDUser object
// This function will return nil if the file is not exist
}
class private func deleteCurrentUserFromDisk() {
// This will delete the current user file from disk
}
}
// MARK: - Helper
extension NSDate {
class func dateFromString(string: String) -> NSDate {
// convert string into NSDate
}
}
Use Case
Now with everything in place, we can use it like this
Non-blocking logging in process
EDUser.loginWithUsername(username: "edward#domain.com",
password: "1234") {
user, error in
if error == nil {
// Login succeeded
} else {
// Login failed
}
}
Logging out
EDUser.logout()
Check whether the user is logged in
if EDUser.currentUser != nil {
// The user is logged in
} else {
// No user logged in
// Show the login screen here
}
Get current user data on any screen
if let currentUser = EDUser.currentUser {
// do something with current user data
}
Store other user as object
let user = EDUser(firstName: "Edward",
lastName: "Anthony",
birthDate: NSDate())

Resources