Retrieve data from new Firebase - xcode

Please help. After migrating to new Firebase I can't retrieve data.
Use this construction:
let ref = FIRDatabase.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
ref.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
let postDict = snapshot.value as! [String : AnyObject]
print("\(postDict)")
})
}
After running I see error:
2016-05-19 10:04:22.264 123a[88652:13688922] The default app has not been configured yet.
2016-05-19 10:04:22.276 123a[88652:13688922] *** Terminating app due to uncaught exception 'MissingDatabaseURL', reason: 'Failed to get FIRDatabase instance: FIRApp object has no databaseURL in its FirebaseOptions object.'
*** First throw call stack:
I read documentation, but can't resolve this problem.
GoogleService-Info.plist I add to project.

I didn't see this answer yet, I had to add the configure call to the AppDelegate init method. So it looks like:
override init() {
super.init()
// Firebase Init
FIRApp.configure()
}

Had the same problem.
I looked for linking problems that are related to the plist but that wasn't the problem.
I thought maybe it has caused because of that my initial view controller is revoked before the configurations are completed.
I solved the problem by experimenting a bit.
My initial view controller was this:
let ref = FIRDatabase.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
I've changed that to this:
var ref = FIRDatabaseReference.init()
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
// Do any additional setup after loading the view, typically from a nib.
}
Crash resolved.

So, with mine, I also had a ref being declared immediately when the view controller was instantiated. I had to make it load after the app had been configured in the app delegate with FIRApp.configure().
Before:
let serverRef = Firebase("firebaseURL")
After:
lazy var serverRef = FIRDatabase.database().reference()
This delays the instantiation of the database reference until its needed, which wont be until viewDidLoad on your initial view controller.

To build on the answer given by #ColdLogic, the reason I had this error was because I had my Firebase database reference being created in an init method on a view controller, not in the viewDidLoad method. Since the init methods for all classes that are instantiated when the app launches are called before the application:DidFinishLaunchingWithOptions method in the AppDelegate, it was causing this crash. Moving this line of code:
class MyViewController {
var firebaseRef: FIRDatabaseReference
required init?(coder aDecoder: NSCoder) {
...
firebaseRef = FIRDatabase.database().reference()
}
override func viewDidLoad() {
...
}
}
to here:
class MyViewController {
var firebaseRef: FIRDatabaseReference
required init?(coder aDecoder: NSCoder) {
...
}
override func viewDidLoad() {
...
self.firebaseRef = FIRDatabase.database().reference()
}
}
solved the problem for me.

I too had a problem with the Firebase Database. Fixed it by adding
import FirebaseDatabase
to my code

Had the same problem today, you need the "firebase_url": "https://xxxxxxxxxx.firebaseio.com" at google-services.json and for that do this steps https://support.google.com/firebase/answer/7015592#android
If you had one file from google cloud platform before, maybe there are some differences and you have to check. For me this works.

In my case I had to change the configure to be called before calling the super applicationDidLaunch:
[FIRApp configure];
[super application:application didFinishLaunchingWithOptions:launchOptions];

I was getting this error until I made FIRApp.configure() the first line in the AppDelegate didFinishLaunchingWithOptions

Make sure you have downloaded the GoogleService-Info.plist file from your Firebase console and added to the root of your project directory.
Once you have added it call this function from didFinishLaunchingWithOptions in AppDelegate:
FIRApp.configure()
Thats it, it should be up and running!

Related

How to make instantiateController(identifier: creator:) work? [duplicate]

I am getting what feels like a bug when trying to custom instantiate a window controller from a storyboard. I am using NSStoryboard.instantiateController(identifier:creator:), which is a new function as of MacOS 10.15. The block of code in question is:
let mainWC = storyboard.instantiateController(identifier: "id") { aDecoder in
MainWindowController(coder: aDecoder)
}
I have SUCCESSFULLY used basically this exact code for custom instantiating the main view controller, and just assigning that view to a new window and a new window controller. That works fine. I can also instantiate the window controller the old fashioned way without custom initialization with instantiateController(identifier:). But when I try the above code for custom instantiation of the window controller I end up with the following error:
Assertion failure in -[NSClassSwapper _createControllerForCreator:coder:]... Custom instantiated controller must call -[super initWithCoder:]
Note that both my custom view controller class (which works) and my custom window controller class MainWindowController (which doesn't work) have implemented the trivial initializer:
required init?(coder: NSCoder) {
super.init(coder: coder)
}
I know that this functionality is new as of OS 10.15, but the documentation says it should work for window controllers AND view controllers, and the error message does not make any sense to me.
I hit the same problem, I thought about it a bit and here is how I worked around it.
First, why do I need this for ? I wanted to inject some dependencies to my view controller hierarchy before it's built from the Storyboard. I guess that's what the API is intended to.
But then, would that method be working, how would I pass the injection information down the view controller hierarchy ?
So, as the method is working without bug for view controllers, I decided to inject the information directly at the root view controller.
So, I have in my storyboard :
A window controller scene named "my-window-controller", which window just points to an empty view controller.
A view controller scene named "root-view-controller", where all the view hierarchy is described.
And wherever I want to create that view controller, I just do :
func instanciateWindowController(storyboard: NSStoryboard) -> NSWindowController {
// Load the (empty) window controller scene
let wcSceneIdentifier = NSStoryboard.SceneIdentifier("my-window-controller")
let windowController = storyboard.instantiateController(withIdentifier: wcSceneIdentifier)
as! NSWindowController
// Load the root view controller using the creator trick to inject dependencies
let vcSceneIdentifier = NSStoryboard.SceneIdentifier("root-view-controller")
let viewController = storyboard.instantiateController(identifier: vcSceneIdentifier,
creator: { coder in
return MyOwnViewController.init(coder: coder,
text: "Victoire !") // just pass here your injection info
})
// Associate the window controller and the root view controller
windowController.contentViewController = viewController
return windowController
}
with
class MyOwnViewController: MSViewController {
init?(coder: NSCoder,
text: String) { // receive here the injection information
print(text) // use the injection information here
super.init(coder: coder)
}
// Not used, but required
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
This is filed as Feedback #FB7626059, if you’d like to pile on (I hit the issue too).

Executing NSApplicationDelegate Code Before ViewController viewDidLoad

My Swift 3, Xcode 8.2 MacOS app loads several tables through web services calls. Since the tables are used by one or more of my seven view controllers, I placed them in the AppDelegate.
The problem is that the AppDelegate methods applicationWillFinishLaunching and applicationDidFinishLaunching run after the ViewController viewDidLoad methods.
As a result the table views show no data. I was able to get it to work correctly by calling the appDelegate method that loads the data from one of the ViewController viewDidLoad methods. Since any of the ViewControllers could be invoked on application start up, I would have to add the call to all of them and some sort of flagging method to prevent redundant loads.
My question is: where can I place code that will execute prior to the ViewControllers loading? The code loads data into multiple arrays of dictionary. These arrays are in the AppDelegate.
I read up on #NSApplicationMain and replacing it with a main.swift. I assume none of application objects would have been instantiated at that point so I couldn't call their methods and don't think my code would be valid outside of a class.
The pertinent part of my appDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
var artists: [[String:Any]]? = nil
var dispatchGroup = DispatchGroup() // Create a dispatch group
func getDataFromCatBox(rest: String, loadFunction: #escaping ([[String: Any]]?) -> Void) {
let domain = "http://catbox.loc/"
let url = domain + rest
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "Get"
let session = URLSession.shared
var json: [[String:Any]]? = nil
dispatchGroup.enter()
session.dataTask(with: request) { data, response, err in
if err != nil {
print(err!.localizedDescription)
return
}
do {
json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [[String: Any]]
}
catch {
print(error)
}
loadFunction(json)
self.dispatchGroup.leave()
}.resume()
}
func loadArtistTable(array: [[String: Any]]?) {
artists = array
}
}
The ViewController code:
override func viewDidLoad() {
super.viewDidLoad()
appDelegate = NSApplication.shared().delegate as! AppDelegate
appDelegate.getDataFromCatBox(rest: "artists.json", loadFunction: appDelegate.loadArtistTable)
appDelegate.dispatchGroup.wait()
artistTable.reloadData()
}
The code works in that the TableView is populated when the window appears. While it's not a lot of code, I would have to duplicate across all my View Controllers.
This a prototype. The production version will have 14 tables and invocations.
I guess my comment should be an answer. So. Why not just make the window containing the table views not be visible on launch? Then in didFinishLaunching, load the table data and then show the window.
I don't think there is any way to do what I want the way it is structured in the question. The ViewController code could be reduced to
appDelegate = NSApplication.shared().delegate as! AppDelegate
appDelegate.getDataFromCatBox(rest: "artists.json", loadFunction: appDelegate.loadArtistTable
by creating a wrapper function in AppDelegate that had the wait in it. It also could contain a flag that indicated that a given table had already been loaded so as not to make a redundant call.
I ended up going with a different approach: I created a super class with singleton subclasses for each table. Now my viewDidLoad method looks like this:
artists.loadTable() // The sublass
artistTable.reloadData()
If any one comes up with a cleaner solution to the original problem, I'll accept their answer in place of mine.

NSCache() is not working properly

I think I am tired of NSCache(). Could not understand what's the problem behind this. Trying to save an array of [AnyObject] to NSCahce(), which I have done using this following line of code.
NSCache().setObject(data, forKey: "News")
And tried to get it back using this way.
if let news = NSCache()("News") as? [AnyObject]
{
}
else
{
// I am always here :)
}
So I was thinking what's the problem with this. After searching a bit in Google I could see that setting totalCostLimit and countLimit will help you solve this problem. So I have set it like this.
NSCache().totalCostLimit = 50000
NSCache().countLimit = 50000
After setting this also, it was not working. So I thought of running this code in main thread, which I have done like this.
dispatch_async(dispatch_get_main_queue()) {
NSCache().setObject(data, forKey: "News")
}
Still it returned nil. Now last but not the least I have created one global instance of NSCache() and called all these operations using that instance. Well, doing like that also didn't give the expected result. It always gave me nil.
What's happening here? I know that NSCache() can store AnyObject values. I am saving lot of images in the same project without any problem, when I am trying to save this it returns nil.
Well this AnyObject contains some custom classes. Is that can be a problem? If yes, how will I save it locally without using CoreData or NSUserdefaults.
How I created an instance globally and accessed these. Created one instance of NSCache in the AppDelegate.swift file but outside of AppDelegate class
let mainCache = NSCache()
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification)
{
mainCache.totalCostLimit = 50000
mainCache.countLimit = 50000
}
}
Later I have used it like this.
mainCache.setObject(data, forKey: "News")
And getting the data back like this.
if let news = mainCache.objectForKey("News") as? [AnyObject]
{
}
else
{
// Always here :)
}
When you write NSCache() you are creating a new NSCache instance. You're doing this on just about every line.
What you need to do is create one instance, let myCache = NSCache(), and then reuse it: myCache.setObject(data, forKey: "News").

'#selector' refers to a method that is not exposed to Objective-C

The new Xcode 7.3 passing the parameter via addTarget usually works for me but in this case it's throwing the error in the title. Any ideas? It throws another when I try to change it to #objc
Thank you!
cell.commentButton.addTarget(self, action: #selector(FeedViewController.didTapCommentButton(_:)), forControlEvents: UIControlEvents.TouchUpInside)
The selector it's calling
func didTapCommentButton(post: Post) {
}
In my case the function of the selector was private. Once I removed the private the error was gone. Same goes for fileprivate.
In Swift 4
You will need to add #objc to the function declaration. Until swift 4 this was implicitly inferred.
You need to use the #objc attribute on didTapCommentButton(_:) to use it with #selector.
You say you did that but you got another error. My guess is that the new error is that Post is not a type that is compatible with Objective-C. You can only expose a method to Objective-C if all of its argument types, and its return type, are compatible with Objective-C.
You could fix that by making Post a subclass of NSObject, but that's not going to matter, because the argument to didTapCommentButton(_:) will not be a Post anyway. The argument to an action function is the sender of the action, and that sender will be commentButton, which is presumably a UIButton. You should declare didTapCommentButton like this:
#objc func didTapCommentButton(sender: UIButton) {
// ...
}
You'll then face the problem of getting the Post corresponding to the tapped button. There are multiple ways to get it. Here's one.
I gather (since your code says cell.commentButton) that you're setting up a table view (or a collection view). And since your cell has a non-standard property named commentButton, I assume it's a custom UITableViewCell subclass. So let's assume your cell is a PostCell declared like this:
class PostCell: UITableViewCell {
#IBOutlet var commentButton: UIButton?
var post: Post?
// other stuff...
}
Then you can walk up the view hierarchy from the button to find the PostCell, and get the post from it:
#objc func didTapCommentButton(sender: UIButton) {
var ancestor = sender.superview
while ancestor != nil && !(ancestor! is PostCell) {
ancestor = view.superview
}
guard let cell = ancestor as? PostCell,
post = cell.post
else { return }
// Do something with post here
}
Try having the selector point to a wrapper function, which in turn calls your delegate function. That worked for me.
cell.commentButton.addTarget(self, action: #selector(wrapperForDidTapCommentButton(_:)), forControlEvents: UIControlEvents.TouchUpInside)
-
func wrapperForDidTapCommentButton(post: Post) {
FeedViewController.didTapCommentButton(post)
}
As you know selector[About] says that Objective-C runtime[About] should be used. Declarations that are marked as private or fileprivate are not exposed to the Objective-C runtime by default. That is why you have two variants:
Mark your private or fileprivate method declaration by #objc[About]
Use internal, public, open method access modifier[About]

Swift + Xcode 6 beta 3 + Core Data = awakeFromInsert not called?

Need help.
I'm creating new Document-based Core Data Cocoa project.
Add entity named 'Entity' into the core data model. Add 'creationDate' propery into it and set its type as Date. And create NSManagedObject subclass from 'Editor' menu.
Now I add into 'Entity.swift' file this code:
override func awakeFromInsert() {
super.awakeFromInsert()
self.creationDate = NSDate()
println("awakeFromInsert called")
}
Now in my NSPersistentDocument subclass I write such a init() method:
init() {
super.init()
var context = self.managedObjectContext
context.undoManager.disableUndoRegistration()
var entity = NSEntityDescription.insertNewObjectForEntityForName("Entity", inManagedObjectContext: context)
context.processPendingChanges()
context.undoManager.enableUndoRegistration()
println("\(entity)")
}
Everything compiles... BUT awakeFromInsert is never called! The interesting part is that 'entity' object ain't nil! It was created, but not initialized. And if I write this line in init method
entity.creationDate = NSDate()
then creationDate property will be set to a current date as expected.
But that's not all. If I debug execution step-by-step I can see that execution enters 'Entity.swift' file, but starts from the top of the file, then immediately drops and returns back to the NSPersistentDocument subclass file.
Tell me, is it a bug? Because I'm tired to fight with this nonsense. Thanks.
Accidentally I got it work: you have to add #objc(YourSubclass) before subclass declaration. I usually did #objc class MySubclass and turned out it does not work (don't know why).
WORKING:
#objc(YourSubclass)
class YourSubclass : NSManagedObject {
...
NOT WORKING:
#objc class YourSubclass : NSManagedObject {
...

Resources