I currently have a JSQMessagesViewController, but am running to the problem of storing my JSQMessages in Parse.com, since Parse.com won't allow this type of object. I've been trying to research ways of creating a class that subclasses PFObject while conforming to JSQMessageData so that I can create messages that are of type PFObject and thus able to be stored in Parse.com.
Below is my Messages class. In my JSQMessagesViewController I would typically call
var newMessage = JSQMessage(senderId: "user", displayName: "user", text: "testing chat")
But now how would I do this with my Messages class?
import Foundation
import UIKit
import Parse
class Message: PFObject, PFSubclassing, JSQMessageData {
var senderId_ : String!
var senderDisplayName_ : String!
var date_ : NSDate
var isMediaMessage_ : Bool
var hash_ : Int = 0
var text_ : String
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
class func parseClassName() -> String {
return "Messages"
}
convenience init(senderId: String?, text: String?) {
self.init(senderId: senderId!, senderDisplayName: senderId, isMediaMessage: false, hash: 0, text: text!)
}
init(senderId: String, senderDisplayName: String?, isMediaMessage: Bool, hash: Int, text: String) {
self.senderId_ = senderId
self.senderDisplayName_ = senderDisplayName
self.date_ = NSDate()
self.isMediaMessage_ = isMediaMessage
self.hash_ = hash
self.text_ = text
super.init()
}
func senderId() -> String? {
return senderId_;
}
func senderDisplayName() -> String? {
return senderDisplayName_;
}
func date() -> NSDate? {
return date_;
}
func isMediaMessage() -> Bool {
return isMediaMessage_;
}
func hash() -> UInt? {
return UInt(hash_);
}
func text() -> String! {
return text_;
}
func messageHash() -> UInt {
return UInt(hash_)
}
}
And my implementation in my ChatView that is a JSQMessagesViewController:
override func viewDidLoad(){
super.viewDidLoad()
var myObject = PFObject(className: "messages")
var myMessage = Message(senderId: "User", text: "Some text")
var messageArray:NSMutableArray = [myMessage]
myObject["myArray"] = messageArray
myObject.save()
}
Currently receiving error
fatal error: use of unimplemented initializer 'init()' for class Message
Related
I have a class named UserManager.
public class UserManager{
static let sharedInstance = UserManager()
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, User>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> User?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> User? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = mapJSONToUser(data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
Notice that I have an object called User, and it's used everywhere in this class.
I'd like to have classes called PostManager and AdminManager, which uses the same logic as the class above.
I could simply copy and paste the code above and replace the object User with Post and Admin. But...obviously this is bad practice.
What can I do to this class so that it accepts any resource? Not just User
The most obvious way to do something like this is to embed all of the generic functionality in a generic class, then inherit your UserManager from that:
protocol Managable {
init(json:JSON)
}
public class Manager<T:Manageable> {
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, T>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> T?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> T? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = T(json:data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
class User : Managable {
required init(json:JSON) {
}
}
class UserManager : Manager<User> {
static var instance = UserManager()
}
Now then, any class that implements the Manageable protocol (ie., it has an init(json:JSON) method can have a Manager class variant. Note that since a generic class can't have a static property, that's been moved into the subclass.
Given that inheritance can hide implementation details, if reference semantics are not needed then a protocol + associated type (generics) implementation using structs might be safer and arguably more "Swifty".
Define your protocol with an associated type (Swift 2.2) or type alias (Swift 2.1):
protocol Manager {
associatedtype MyManagedObject // use typealias instead for Swift 2.1
func getResource(id: Int, clientName: String) -> MyManagedObject?
func createResource(data: JSON, clientName: String) -> MyManagedObject?
func releaseResource(id: Int, clientName: String)
}
And then your implementation becomes:
public struct UserManager: Manager {
typealias MyManagedObject = User
func getResource(id: Int, clientName: String) -> User? { ... }
func createResource(data: JSON, clientName: String) -> User? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
And you can further add objects using the same protocol easily, specifying what 'MyManagedObject' should be:
public struct PostManager: Manager {
typealias MyManagedObject = Post
func getResource(id: Int, clientName: String) -> Post? { ... }
func createResource(data: JSON, clientName: String) -> Post? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
I would recommend reading up more on protocols and generics in detail (there are many examples online, Apple's documentation is a good place to start).
I get this error (fatal error: NSArray element failed to match the Swift Array Element type) whenever I delete items from my collection view, close the app, relaunch the app, and then go back to the project. I've found similar solutions that re-ordered the initializers but that doesn't seem to wok for me.
**AppDelegate.swift**
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// register PFObject subclasses
Task.registerSubclass()
Task.initialize()
Project.registerSubclass()
Project.initialize()
User.registerSubclass()
User.initialize()
**Project.swift**
import UIKit
import Parse
public class Project: PFObject, PFSubclassing
{
// MARK: - Public API
#NSManaged public var projectTitle: String!
#NSManaged public var owner: User!
#NSManaged public var tasks: [Task]
// MARK - Required PFSubclassing Parse setup
override public class func initialize()
{
struct Static {
static var onceToken: dispatch_once_t = 0
}
dispatch_once(&Static.onceToken) {
Task.initialize()
self.registerSubclass()
}
}
public static func parseClassName() -> String {
return "Project"
}
// MARK: Conveinience Init
init(projectTitle: String, owner: User, tasks: [Task])
{
super.init()
self.projectTitle = projectTitle
self.owner = owner
self.tasks = tasks
}
override init() { super.init() }
}
**Task.swift**
import UIKit
import Parse
public class Task: PFObject, PFSubclassing
{
// MARK: - Public API
#NSManaged public var title: String
#NSManaged public var isChecked: Bool
#NSManaged public var projectOwner: Project
#NSManaged public var category: String
#NSManaged public var isImportant: Bool
// MARK - Required PFSubclassing Parse setup
override public class func initialize()
{
struct Static {
static var onceToken: dispatch_once_t = 0
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
public static func parseClassName() -> String {
return "Task"
}
// MARK: Conveinience Init
// Create new project
init(title: String, projectOwner: Project, isChecked: Bool, category: String, isImportant: Bool)
{
super.init()
self.title = title
self.projectOwner = projectOwner
self.isChecked = isChecked
self.category = category
self.isImportant = isImportant
}
override init() { super.init() }
}
The error specifically happens when I try to use the tasks array. I load it into a UICollectionView.
Here's possibly relevant code where I grab the tasks from Parse:
// Fetch data from Parse
func fetchProject() {
let isCurrentTab = navigationController?.tabBarItem.title
let tasksQuery = PFQuery(className: Task.parseClassName())
tasksQuery.whereKey("category", equalTo: isCurrentTab!)
tasksQuery.whereKey("projectOwner", equalTo: project)
tasksQuery.findObjectsInBackgroundWithBlock { (tasks, error) -> Void in
if error == nil {
self.project.tasks.removeAll()
print(tasks)
for task in tasks! {
print(task)
let task = task as! Task
self.project.tasks.append(task)
}
self.collectionView?.reloadData()
} else {
print("\(error?.localizedDescription)")
}
}
}
I'm using Xcode 7.1 and Parse v1.8.5
Any help would be much appreciated!
I ended up using pointers to access my tasks array instead of having it nested in the projects array. That way, I could make all of the calls directly to that array and not lose anything by trying to go down to the nested level.
I have a class UserFeed where I store all the posts. And I have a class UserProfile where I store all the user details(name, age, occupation). Currently I have a pointer to UserProfile. But when I try to set the occupationLabel it gives me nil.
// PostsCollectionViewCell.swift
import UIKit
import DateTools
import Parse
class PostsCollectionViewCell: UICollectionViewCell {
var post: Post! {
didSet {
updateUI()
}
}
#IBOutlet var postLabel: UILabel!
#IBOutlet var genderLabel: UILabel!
#IBOutlet var occupationLabel: UILabel!
#IBOutlet var timeLabel: UILabel!
#IBOutlet var likeButton: UIButton!
func layoutSubview() {
super.layoutSubviews()
}
private func updateUI() {
occupationLabel?.text! = post.userProfile.occupation
timeLabel?.text! = post.createdAt?.shortTimeAgoSinceDate(NSDate()) ?? ""
postLabel?.text! = post.postText
}
#IBAction func likeButtonDidTouch(sender: AnyObject) {
}
}
Post query in my DiscoverViewController
func queryForPosts() {
PFGeoPoint.geoPointForCurrentLocationInBackground { (geopoint, error) in
if !(error != nil) {
if let geoPoint = geopoint {
let query = PFQuery(className: "UserFeed")
query.whereKey("location", nearGeoPoint: geoPoint, withinMiles: 5)
query.addDescendingOrder("createdAt")
query.includeKey("userProfile")
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil {
if let postObjects = objects as? [PFObject] {
self.posts.removeAll()
for postObject in postObjects {
let post = postObject as! Post
self.posts.append(post)
}
self.collectionView.reloadData()
}
} else {
print("\(error!.localizedDescription)")
}
})
}
}
}
}
Post subclassing
import UIKit
import Parse
public class Post: PFObject, PFSubclassing{
// MARK: - Public API
#NSManaged public var username: PFUser
#NSManaged public var location: PFGeoPoint?
#NSManaged public var userProfile: String!
#NSManaged public var postText: String!
#NSManaged public var numberOfLikes: Int
#NSManaged public var likedUserIdCollection: [String]!
public func incrementNumberOfLikes() {
numberOfLikes++
self.saveInBackground()
}
//Mark: - Convience init
init(username: PFUser, location: PFGeoPoint?, userProfile: String, postText: String, numberOfLikes: Int) {
super.init()
self.username = username
self.location = location
self.userProfile = userProfile
self.postText = postText
self.numberOfLikes = numberOfLikes
self.likedUserIdCollection = [String]()
}
override init() {
super.init()
}
//MARK: - Like / Dislike
public func like(){
let currentUserObjectId = PFUser.currentUser()!.objectId!
if !likedUserIdCollection.contains(currentUserObjectId) {
numberOfLikes++
likedUserIdCollection.insert(currentUserObjectId, atIndex: 0)
self.saveInBackground()
}
}
public func dislike() {
let currentUserObjectId = PFUser.currentUser()!.objectId!
if likedUserIdCollection.contains(currentUserObjectId) {
numberOfLikes--
for (index, userId) in likedUserIdCollection.enumerate() {
if userId == currentUserObjectId {
likedUserIdCollection.removeAtIndex(index)
break
}
}
self.saveInBackground()
}
}
// MARK: - PFSubClassing
override public class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
public static func parseClassName() -> String {
return "UserFeed"
}
}
Object can't store value until it intialize, need to call below init as present in your code
init(username: PFUser, location: PFGeoPoint?, userProfile: String, postText: String, numberOfLikes: Int) {
super.init()
self.username = username
self.location = location
self.userProfile = userProfile
self.postText = postText
self.numberOfLikes = numberOfLikes
self.likedUserIdCollection = [String]()
}
Need to initialize Post object, seems you have not made it.
If anyone has any experience working with Parse using Swift, specifically subclassing PFObject..... I cannot figure out why the saveinbackground call below is throwing the above error?
Thanks!
func saveNewPerson(name: String) {
var myPeeps = [Person]()
if let currentUser = PFUser.currentUser() {
if currentUser.valueForKey("myPeeps")?.count < 1 {
myPeeps = []
} else {
myPeeps = currentUser.valueForKey("myPeeps") as! [Person]
}
let newPerson = Person(name: name, stores: [:])
myPeeps.append(newPerson)
currentUser.setObject(myPeeps, forKey: "myPeeps")
println(currentUser.valueForKey("myPeeps")?.count)
//WHY DOES THIS SAVE THROW ERROR FOR NOT INITIALZING?
currentUser.saveInBackgroundWithBlock{ succeeded, error in
if succeeded {
//3
println("Saved")
} else {
//4
if let errorMessage = error?.userInfo?["error"] as? String {
self.showErrorView(error!)
}
}
}
}
}
This is my Person class:
class Person: PFObject, PFSubclassing {
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
static func parseClassName() -> String {
return "Person"
}
var name: String = ""
var stores: [String : Store] = [:]
init(name: String, stores: [String : Store]) {
self.name = name
self.stores = stores
super.init()
}
}
My Store Class:
class Store: PFObject, PFSubclassing {
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
static func parseClassName() -> String {
return "Store"
}
var name: String = ""
var clothingSizes: [String: String] = [:]
init(name: String, clothingSizes: [String: String]){
self.name = name
self.clothingSizes = clothingSizes
super.init()
}
}
For both Parse subclasses, you need to make your inits convenience inits. Basically, what's going on is there is no implementation of init(), which you could do, by calling
override init() {
super.init()
}
Another option is to make your init a convenience init, and calling self.init()
convenience init(name: String, stores: [String : Store]) {
self.init()
self.name = name
self.stores = stores
}
I'm pretty new to iOS/Swift/Parse and I'm trying to build a model of a class using PFSubclassing.
The data I'm trying to represent should look something like this
{
text: ""
location : {
name: "",
longitude: "",
latitude: ""
}
}
So fare the model I'm have is
class LocationModel {
var name: String?
var longitude: Float?
var latitude: Float?
}
class PostModel: PFObject, PFSubclassing {
class func parseClassName() -> String! {
return "Post"
}
#NSManaged var text: String?
var location: LocationModel?
}
The test property is being saved successfully but I'm unable to get the location properties to save.
The code I'm using to save a record to parse is
var test = PostModel()
test.location?.name = "ESB"
test.location?.latitude = 1
test.location?.longitude = 1
test.text = "This is a test post to see if this works!"
test.saveEventually { (success: Bool, error: NSError!) -> Void in
println(error)
println(success)
}
I did a lot of digging online but I'm unable to find a solution on how to represent an Object datatype in Swift using Parse PFSubclassing
Any help would be greatly appreciated.
Thank you
Here's my solution:
I will create a Hero object for example.
class Hero: PFObject, PFSubclassing {
#NSManaged var strengthPoint: Double
#NSManaged var name: String
static func parseClassName() -> String {
return "Hero"
}
init(strengthPoint: Double, name: String) {
super.init()
self.strengthPoint = strengthPoint
self.name = name
}
init(pfObject: PFObject) {
super.init()
self.strengthPoint = pfObject.object(forKey: "strengthPoint") as! Double
self.name = pfObject.object(forKey: "name") as! String
}
override init() {
super.init()
}
override class func query() -> PFQuery<PFObject>? {
let query = PFQuery(className: self.parseClassName())
query.order(byDescending: "createdAt")
query.cachePolicy = .networkOnly
return query
}
}
Now, after defining your model, you can use these methods to store and retrieve
Create your object in server
func createHero() {
let hero = Hero(strengthPoint: 2.5, name: "Superman")
hero.saveInBackground { (isSuccessful, error) in
print(error?.localizedDescription ?? "Success")
}
}
Retrieve object from server
func retrieveHero() {
let query = Hero.query()
query?.getFirstObjectInBackground(block: { (object, error) in
if error != nil {
print(error?.localizedDescription ?? "Something's wrong here")
} else if let object = object {
let hero = Hero(pfObject: object)
print(hero.strengthPoint) // 2.5
print(hero.name) // Superman
}
})
}
I have seen several different methods for PFSubclassing in Swift 1.2, but the following works best for me:
To begin with, make sure that you have the following in your Objective-C Bridging Header:
#import <Parse/PFObject+Subclass.h>
Here is a very basic example of subclassing PFObject:
import Foundation
import Parse
class Car: PFObject, PFSubclassing {
override class func initialize() {
self.registerSubclass()
}
static func parseClassName() -> String {
return "Car"
}
#NSManaged var model: String
#NSManaged var color: String
#NSManaged var yearManufactured: Int
}
So in your case, this would be:
class PostModel: PFObject, PFSubclassing {
override class func initialize() {
self.registerSubclass()
}
static func parseClassName() -> String {
return "Post"
}
#NSManaged var text: String?
}
Concerning your LocationModel...I'm a bit confused as to what exactly you are trying to accomplish with that. I hope this helps.