iOS Populate Table View With Firebase Data Using MVC Pattern - swift

In my Firebase project I'm trying to apply Model View Controller pattern, so I separated the controller and the class that handles firebase requests.
I get this exception
exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber length]: unrecognized selector sent to instance
I'm trying to get news items from the database. Here is the model
class NewsItem: NSObject{
var title: String?
var detail: String?
var photoUrl: String?
var timestamp: String?
}
Here is the firebase handler class
protocol NewsController: class {
func fetchedNews(_ newsItem: NewsItem)
}
class FirebaseHandler {
private static let _instance = FirebaseHandler()
static var Instance: FirebaseHandler{
return _instance
}
weak var newsControllerDelegate: NewsController?
func fetchNews() {
References.Instance.newsRef.observe(.childAdded) {
(snapshot: DataSnapshot) in
if let child = snapshot.value as? [String: AnyObject]{
let newsItem = NewsItem()
print("CHILD: \n\n\n\n\(child)\n\n\n")
newsItem.setValuesForKeys(child)
DispatchQueue.main.async {
self.newsControllerDelegate?.fetchedNews(newsItem)
}
}
}
}
}
I can get the child values printed fine, but the problem is when I call the protocol delegate method.
Here are some portions of the table view controller class where I adopt the NewsController protocol:
FirebaseHandler.Instance.newsControllerDelegate = self
FirebaseHandler.Instance.fetchNews()
Then I implement the method:
func fetchedNews(_ newsItem: NewsItem) {
print("Item:\n\n\n\(newsItem)\n\n\n")
self.newsItems.append(newsItem)
self.tableView.reloadData()
}
The newsItem isn't printed since the error occurs before this method is called I guess. Any suggestions are appreciated.

From the reported NSNumber related error, I would guess your timestamp property is actually stored as an integer in Firebase (and not as a string). If this is the case, try changing it to:
var timestamp: Int = 0
To understand why we can't use Int? (or even Int!) above please see this answer as well.
Also: you don't need that DispatchQueue.main.async wrapper in your observer code. All database callbacks are already called on the main thread by Firebase ;)

Related

NSUnknownKeyExeption using firebase and NSobject

2018-12-30 15:01:23.228731+0200 iChat[51679:726127] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key username.'
(lldb)
Using firebase dictionary to setvalue for a key to NSobject class
import UIKit
class User: NSObject {
var email: String?
var username: String?
}
Function
func fetchUsers() {
Database.database().reference().child("users").observe(.childAdded) { (snap) in
if let dictionary = snap.value as? [String : AnyObject]{
let user = User()
user.setValuesForKeys(dictionary)
print(user.username)
}
}
}
Objective-C inference has been changed in Swift 4. You have to add the #objc attribute to each property
class User: NSObject {
#objc var email: String?
#objc var username: String?
}
However setValuesForKeys is very objective-c-ish. There are better (and more light-weight) ways in Swift without the heavy ObjC runtime.
And why are both properties optional? Are users allowed without name and email?
Consider that with non-optionals the compiler will throw an error at compile time if you are going to pass nil.

Extend Private class defined in an extension of class in Swift

TL;DR
Is it possible to extend a privately owned and defined-in-extension class, i.e. NewsParser?
Related documents
swift2 - Extension of a nested type in Swift - Stack Overflow talks about similar situation, except the nested class type is not private.
I have a class NewsPost:
class NewsPost {
var title: String?
var author: String?
var mainContent: NSAttributedString?
var data: Data? {
didSet {
let newsParser = NewsParser(delegate: self)
newsParser.parse()
}
}
// Init methods and other stuff...
}
And a NewsPost-owned class NewsParser: (in another Swift file, but this does not seem to be a factor, due to SR-631)
private extension NewsPost {
private class NewsParser {
weak var delegate: NewsPost?
// Other properties for parsing...
init(delegate: NewsPost) {
self.delegate = delegate
}
func parse() {
// parse the delegate.data and update properties in delegate (NewsPost instance)
}
// Other methods to be called for parsing...
}
}
But it does not seem to possible to extend NewsPost.NewsParser.
The following attempts do not work:
Attempt 1
Error: 'NewsParser' is inaccessible due to 'fileprivate' protection level
private extension NewsPost { // Notice the "private" prefix
class NewsParser {
weak var delegate: NewsPost?
//Other properties for parsing...
init(delegate: NewsPost) {
self.delegate = delegate
}
func parse() {
// parse the delegate.data and update properties in delegate (NewsPost instance)
}
// Other methods to be called for parsing...
}
}
Error happens in NewsPost definition:
var data: Data? {
didSet {
let newsParser = NewsParser(delegate: self) // error happens here
newsParser.parse()
}
}
Attempt 2
Error: 'NewsParser' is inaccessible due to 'private' protection level
extension NewsPost {
private class NewsParser { // Notice the "private" prefix
var delegate: NewsPost
// Other properties for parsing...
func parse() {
// parse the delegate.data and update properties in delegate (NewsPost instance)
}
// Other methods to be called for parsing...
}
}
extension NewsPost.NewsParser { // error happens here
// extensions here...
// many kinds of errors happen here
}
Is it possible to extend a privately owned and defined-in-extension class, i.e. NewsParser?
I tried your code in a playground and it worked like a charm with a private class nested in a private extension :
Called that way :
var str = "Hello, playground"
let post = NewsPost()
post.data = str.data(using: .utf8)
Your main problem is that your probably declared your private extension in a separate file and private means fileprivate for an extension. Put your extension and your NewsPostclass in the same file and your error should go away!
If you really want to extend NewsParser you have to make it internal.
Extension declaration are only valid at file scope so if you create a private class you have no way of extending it.
Note that an internal nested class would not be visible outside its target. So using Frameworks you should be able to hide your NewsParser class from your UI code.

NSObject setValuesForKeys coding-complaint error in Swift 4.0.2

I usually would create an NSObject like the example below make a firebase observe single event call to get data, then I would create an instance of the NSObject and simply call the setValueForKeys method on the NSObject and the values will be passed in then I could easily use an if let statement to get the specific data I required. this has stopped working since I upgraded to swift 4.0.2 from swift 3.1 see code snippet below. I believe I am doing this wrong way, since the new update. As the key value it requires does exist as can be seen in the NSObject declaration and also the print out. Any suggestion will be appreciated.
class BarberMarker: NSObject {
var companyID: String?
var companyName: String?
var companyLogoImageUrl: String?
var companyLogoImage: UIImage?
var companyFormattedAddress: String?
var companyAddressLongitude: String?
var companyAddressLatitude: String?
var distanceFromCurrentUser: Double?
var dateCreated: String?
var timezone: String?
var calendar: String?
var local: String?
var ratingValue: CGFloat?
}
firebase call to get data from database
func getAllMarkers(){
firebaseRef.child("barberMarkers").observeSingleEvent(of: .value, with: { (snapshotssshhh) in
if let dictionary = snapshotssshhh.value as? [String: AnyObject] {
for marker in dictionary {
if let locMarker = marker.value as? [String: AnyObject] {
var markerB = BarberMarker()
print(locMarker)
markerB.setValuesForKeys(locMarker)
self.barbermarkers.append(markerB)
guard let lon = markerB.companyAddressLongitude, let lat = markerB.companyAddressLatitude else {
return
}
let latitude = (lat as NSString).doubleValue
let longitude = (lon as NSString).doubleValue
let locValue:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
DispatchQueue.main.async{
let desiredMarker = GMSMarker(position: locValue)
desiredMarker.icon = GMSMarker.markerImage(with: UIColor(r: 118, g: 187, b: 220))
desiredMarker.map = self.mapView
self.bMarkers.append(desiredMarker)
}
}
}
}
}, withCancel: nil)
}
error message I get
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<maizewars.BarberMarker 0x7fb62ff5f5b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key companyAddressLongitude.'
try this bro.
#objcMembers class BarberMarker: NSObject {
...
}
reason #objcMembers Use in Swift 4
When a Swift class introduces many new methods or properties that require behavior from the Objective-C runtime, use the #objcMembers attribute in the declaration of that class.
Applying the #objcMembers attribute to a class implicitly adds the #objc attribute to all of its Objective-C compatible members.
Because applying the #objc attribute can increase the compiled size of an app and adversely affect performance, only apply the #objcMembers attribute on declarations when each member needs to have the #objc attribute applied.

NSPredicate NSUnknownKeyException - Swift 4.0

I have my current code as listed below:
class ViewController:UIViewController{
ovveride func viewDidLoad(){
filterData()
}
func filterData(){
var usersArray = [User(name:"John",userId:1),User(name:"Ed",userId:2),User(name:"Ron",userId:3)]
let filteredData = (userArray as NSArray).filtered(using:NSPredicate(format: "userId=1"))
}
}
The above code throws throws the following Exception (NSUnknownKeyException):
The Object Type '[<ViewController.User 0x6000000afa20> valueForUndefinedKey:]':
this class is not key value coding-compliant for the key userId.
The document of Apple for filter(using:) does not specify any changes that could be causing this issue.
How can I use NSPredicate in Swift 4.0?
Also, as requested, I tried using #objc. But, still the same error.
#objc
class User:NSObject{
var name:String!
var userId:Int!
init(name:String,userId:Int){
self.name = name
self.userId = userId
}
}
With Further Comments received for this question, on adding #objc attribute to the userId I receive the below message.
property cannot be marked #objc because its type cannot be represented in Objective-C
#objc
class User:NSObject{
#objc var name:String!
var userId:Int! //#objc here results in error
init(name:String,userId:Int){
self.name = name
self.userId = userId
}
}
String property NSPredicate it works completely fine.
- Add #objc to class
- Also, add #objc to property
Why not just use filter? It's much more "Swift-y" and doesn't involve converting your array to an NSArray.
let filteredData = usersArray.filter { $0.userId == 1 }

Realm Cannot invoke 'add' with an argument list of type '(Person)'

This error is so frustrating. I'm just trying to add an object to my Realm database and I have gotten to the point of just copying and pasting example code and it will not work. So I have an add person method that does this:
func addPerson(person person:Person){
realm.beginWrite()
realm.add(person)
realm.commitWrite()
}
And the realm variable is stored like this in the class header:
private var realm:Realm
Being initialized in the init() method as such:
realm = Realm()
My actual person class looks like this:
import UIKit
class Person {
var name:String?
var relation: Relations?
var title: String?
var importance:Double?
var events:Array<Event>?
var image:UIImage?
init(name:String,relation:Relations?,events:Array<Event>?,image:UIImage?){
self.relation = relation
if relation != nil{
self.title = relation!.title
self.importance = relation!.importance
}
else{
self.title = nil
self.importance = nil
}
self.events = events
self.image = image
self.name = name
}
init() {
}
}
The error is so frustrating because it seems like the Person class does not conform to the Object superclass, but in reality, that's the only option
This is just a summary of what I said in the comments:
Use Object as a subclass as it is necessary for a class to be a model in Realm.