Okay, so basically I am following a tutorial on udemy on how to create a chat with Backendless and Firebase. However, I prefer not to use Backendless, because I don't want to rely on 2 providers - so I want to stick to Firebase only. Therefore, I am currently converting my code to Firebase.
I have a view controller that displays a unique page for each UID - from a database that I have. The UID is stored as a String, and is assigned upon a segue from another table view controller (this works fine). After that, I fetch the data that I want from the user, with the UID. I have a "Start Chat" button that is supposed to create a new chat.
In this tutorial, the tutor has set a protocol (delegate) that is triggering another function from another view controller. This is what it looks like:
protocol ChooseUserDelegate {
func createChatroom(withUser: String)
}
var delegate: ChooseUserDelegate!
and in my chat #IBAction, I have this code:
#IBAction func StartChat(sender: AnyObject) {
let userID = uid
if let theId = userID as? String {
delegate.createChat(String(theId))
}
}
(The code above is all in the same VC.).
In another view controller, where the createChat() function is stored, is the following code:
class AnotherVC UIViewController, UITableViewDelegate, UITableViewDataSource, ChooseUserDelegate{
func createChat(withUser: String) {
print(withUser)
}
}
The problem is that I can't get to call createChat(), because of an optional error (unwrapping) on the delegate.createChat(String(theId)).
Edit: Even with a "" input, I get an error. I am really confused now. Is it something wrong with my delegate?
The only part of your code that is optional is delegate (because you correctly unwrapped userID). Therefore, the error must be due to delegate being nil. Make sure that you set delegate before calling StartChat().
The line var delegate: ChooseUserDelegate! does not initialize a delegate. When you write ChooseUserDelegate! you are only defining the type of the delegate variable. It is automatically set to nil. To initialize a new instance of ChooseUserDelegate you would need to write something like:
var delegate: ChooseUserDelegate! = ChooseUserDelegate()
There are a few other ways you could clean up your code. Method names should be llamaCase, not CamelCase, so you should rename StartChat() to startChat() (be sure to reconnect in interface builder). The body of that method has three different names for the same variable, uid. See how simple it could be:
#IBAction func startChat(sender: AnyObject) {
if let uid = uid as? String {
delegate.createChat(uid)
}
}
if let theId = userID {
delegate.createChat(String(theId))
}
Related
Heres a caption of my API call:
So, I've got the abilities of the pokemons I needed, but now, idk how to get that data out of my Service class (where I'm doing all the parsing), and send it to my InfoViewController.
My purpose is to fetch that data on some label, and then show the ability names for every poke, according to their ID. Here is a caption of my app:
I wanna add an "Ability" label below Weight, and that's where I wanna assign the data. I have a whole CollectionView with all the pokemons, and the goal is assign the correct ability for each one of them.
I'm kinda struggling for a practical (and less verbose) way to reach this.
I apreciated every comment, any advice and suggestion too. Thanks!
EDIT: Heres my code:
extension InfoController: ServiceDelegate {
func finishedWithPokemonAbilities(abilities: [String], id: Int) {
self.abilities = abilities
self.ids = id
print(abilities)
}
}
You can create a custom Protocol(could call it PokemonServiceDelegate as an example) that your InfoViewController would inherit and implement. On your service object(I'm using PokemonService in the example) create a property with a type of PokemonServiceDelegate and set that property to the view controller that you want to receive the data. After the service finishes fetching the data, update the delegate by passing the data in the function declared in the protocol.
// Protocol your view controller will inherit
protocol PokemonServiceDelegate {
// Function your view controller will implement
func finishedWithPokemonAbilities(abilities: [String])
}
class InfoViewController: UIViewController {
// Reference to the service that makes the request
var service: PokemonService
override func viewDidLoad() {
...
// Set the delegate of the service to self
service.delegate = self
...
}
}
extension InfoViewController: PokemonServiceDelegate {
// Implement the protocol
func finishedWithPokemonAbilities(abilities: [String]) {
// Do something with their abilities here
}
}
struct PokemonService {
var delegate: PokemonServiceDelegate?
// The function that you call to get your abilities
func someUpdateFunc() {
...
let abilities = json[abilities].arrayValue.map {$0["ability"]["name"].stringValue}
delegate?.finishedWithPokemonAbilities(abilities: abilities)
...
}
}
I have a block of code
#IBAction func postbutton(_ sender: UIButton) {
let ref: DatabaseReference = Database.database().reference()
let postvalue = ref.child("All Post").childByAutoId()
let postkeyvalue = postvalue.key!
postvalue.child("Post Content").setValue(postcontent.text)
postvalue.child("Post Author").setValue(currentuser)
var possstkeystring = postkeyvalue
}
I need to access the possstkeystring in my other view controller file.
I have already tried declaring the possstkeystring outside of the function. When I do that, my project always fail because it's empty.
I have tried using the prepare function that didn't work.
I tried declaring the variable outside of the class- that didn't work.
It always failed because the string was empty, and since its a firebase child string, it cant be empty.
Here is my other file block of code I'm trying to plug this into
self.ref.child("All Post").child(possstkeystring).child("Post Author").observe(DataEventType.value, with: { (snapshot) in
if let item = snapshot.value as? NSDictionary
{
self.authornamereference = item["Post Author"] as! String
}
please help! thanks :)
When I actually input the variable outside of scope or in a static variable, I get this error
Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(child:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''
You can do it in multiple ways, One of the ways is to use NotificationCenter
Using NotificationCenter:
First you need to add extension to Notification.Name anywhere in your App. Like
extension Notification.Name { static let mynotification = Notification.Name("mynotification") }
In your Another View controller viewDidLoad method add
NotificationCenter.default.addObserver(self, selector: #selector(yourMethod), name: NSNotification.Name.mynotification, object: nil)
Then in your Another ViewController add a method which will be called when notification fired
func yourMethod(){
//// do something
}
Now in your Click event method or even anywhere from app, you can call viewController's method by sending notification Like
NotificationCenter.default.post(name: NSNotification.Name.mynotification, object: nil)
Another Easy way to, if you started Another ViewController from this same viewController where the click event is present, then You must have the reference of the viewController. then you need to just call
vc.yourMethod(params);
On Watch, I'm able to pass a saved workout from the WorkoutInterfaceController to the SummaryInterfaceController. But I was wondering how to pass the saved workout from the Watch to the iPhone (so I can display it in a Summary View Controller too).
Do you know? Or is there a better way I'm supposed to do this?
Here's what I use to pass the saved workout from WorkoutInterfaceController to the SummaryInterfaceController:
private func saveWorkout() {
// Create and save a workout sample
let configuration = workoutSession!.workoutConfiguration
let isIndoor = (configuration.locationType == .indoor) as NSNumber
print("locationType: \(configuration)")
let workout = HKWorkout(activityType: configuration.activityType,
start: workoutStartDate!,
end: workoutEndDate!,
workoutEvents: workoutEvents,
totalEnergyBurned: totalEnergyBurned,
totalDistance: totalDistance,
metadata: [HKMetadataKeyIndoorWorkout:isIndoor]);
healthStore.save(workout) { success, _ in
if success {
self.addSamples(toWorkout: workout)
}
}
WKInterfaceController.reloadRootControllers(withNames: ["SummaryInterfaceController"], contexts: [workout])
}
private func addSamples(toWorkout workout: HKWorkout) {
// Create energy and distance samples
let totalEnergyBurnedSample = HKQuantitySample(type: HKQuantityType.activeEnergyBurned(),
quantity: totalEnergyBurned,
start: workoutStartDate!,
end: workoutEndDate!)
// Add samples to workout
healthStore.add([totalEnergyBurnedSample], to: workout) { (success: Bool, error: Error?) in
if success {
// Samples have been added
}
}
}
Let me know if any questions or information needed, thanks!
As a part of my research and development,I discovered how the pairing of the iPhone and the Apple Watch has the potential to be useful.
In this case, tapping on a button on the Watch app will send text on the iPhone.
To make a simple demo of this functionality, place a button on the WatchKit interface and a label on the iOS app’s storyboard.
Now, hook up the button to the WatchKit Interface Controller as an IBAction in order to respond to button tap events. Also hook up the Label to the UI View Controller as an IBOutlet.
In the Interface Controller, we make up a string variable to send to the label and in the button’s IBAction method, make a dictionary that includes the string variable you made. This dictionary is what is passed to the iPhone app.
class InterfaceController: WKInterfaceController {
Var str: String = "Hello Phone"
#IBAction func button() {
let dict: Dictionary = ["message": str]
}
Use the following method to send the dictionary to the iPhone.
WKInterfaceController.openParentApplication(dict, reply: {(reply, error) -> void in
print("Reply receive from iPhone app")
})
In the AppDelegate of the iOS app, add the following application method. This is what will handle the previous methods communication from the Watch. Also we can use a notification to notify a view controller that data has been received and to pass it along.
func application(application: UIApplication, handleWatchkitExtensionRequest userInfo: [NSObject : AnyObject]?, reply:(([NSObject : AnyObject]!) {
NSNotificationCenter.defaultCenter().postNotificationName("WatchKitReq", object: userInfo)
}
Finally in the view controller, make a listener for the notification that will update the label’s text to the string that was sent with the data.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector("handleWatchKitNotification:"), name: "WatchKitReq", object: nil)
}
func handleWatchKitNotification(notification: NSNotification) {
if let userInfo = notification.object as? [String : String] {
label.text = userInfo["message"]
}
}
Hope this will help you to understand. For more concerns you can look on this,
Delegate Method
To do this you have to create an App Group which is essentially a space which both apps can use. It was brought in with the exetension framework in iOS8 so apps can communicate with their Today widgets, or custom keyboards, and amongst other applications.
ADD CAPABILITIES
The first thing we have to do is add the app group capability to both our iPhone and Watch Watch Extension targets.
To do this open up the project settings (blue icon at the top of the list of files) and select the iPhone target. You will need to select the “capabilities” tab at the top of the page and scroll down to turn on app groups.
This requires a connected developer profile, and will take a while to enable. You’ll need to do the same steps to switch on app groups for the watch kit extension also.
Next you need to ensure that the app group string is an identifier string you want and that makes sense for your app, it must start with the word group or it complains. You can also add multiple groups if you wish. Whatever you pick they must be enabled with a blue tick (again this might take a while) and are exactly the same for both the iPhone and Watch extension targets!
To use App Groups, it’s not that different or difficult to use than NSUserDefaults:
var userDefaults = NSUserDefaults(suiteName: "group.com.example.My-App")
userDefaults.setObject(true, forKey: "isDarkModeEnabled")
userDefaults.synchronize()
The only differences here are how NSUserDefaults is instantiated and calling synchronize at the end. You feed it the container ID to the constructor parameter called “suiteName”, then call “synchronize()”, and your data flies to the cloud for other apps and devices to consume.
Taking It to the Next Level
You can take this one step further by creating a class for your app and abstract the underlying storage for your properties. Here’s an example:
public class ConfigurationModel: NSObject {
public static let storageKey = "group.com.example.My-App"
public let userStorage = NSUserDefaults(suiteName: storageKey)
public var isDarkModeEnabled: Bool {
get {
// Get setting from storage or default
if userStorage?.objectForKey("isDarkModeEnabled") == nil {
userStorage?.setObject(false, forKey: "isDarkModeEnabled")
userStorage?.synchronize()
}
return userStorage?.objectForKey("isDarkModeEnabled")
}
set {
// Set new value in storage
userStorage?.setObject(newValue, forKey: "isDarkModeEnabled")
userStorage?.synchronize()
}
}
At the top of the class, I am declaring my group container ID and creating the NSUserDefault object out of it. Then my properties for the class have getters and setters to store the data to the App Group. If the key doesn’t exist, it creates it with a default value and synchronizes it. Using the class from this point forward is simple:
var configModel = ConfigurationModel()
configModel.isDarkModeEnabled = true
This property is stored in the cloud! Everything is abstracted away for you. You don’t have to be bothered about storing and synchronizing it into the App Group. It’s all done automatically for you in the getters and setters!
Hope, this will help you to understand how you can share data between the iPhone and Apple Watch app.
Im having a hard time understanding data passing with delegates, I have looked but I get confused by the answers as I'm usually trying to do the opposite of what needs to be done for mine.
What I am trying to do is pass a PFObject from my main viewController (a UITableView) to a PopOverViewController.
I have done this successfully, but I need to pass the object. What would be the best way to pass from the mainController (SOITableViewController) to the popover (DetailPopViewController)?
Where should the protocol go? Where should the Delegate method be placed, etc.
Thank you!
PrepareForSegue, NSUserDefault and Singleton
You have a few possible options to pass your data to other views depending how you want that data to be handled, I will explain each for you and you can choose which one best fit your need.
prepareForSegue: Method
I recommend this method if you want to hold your data for 1 segue transition, it's a good cause to pass this again to another view afterward you need to create another prepareForSegue within the new view. here is an example on how to do this:
First, you create 2 variables in both views, 1 to send (currentViewController.swift) and 1 to receive (toViewyourGoingController.swift).
currentViewController.swift var dataToSend: AnyObject?
ViewYourGoingController.swift var dataToReceive: AnyObject?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Check your segue, this way you can transfer different data to different view. also make sure the identifier match your segue.
if segue.identifier == "toViewYourGoing" {
//Initial your second view data control
let ExchangeViewData = segue.destinationViewController as! toViewyourGoingController
//Send your data with segue
ExchangeViewData.dataToReceive = dataToSend
}
}
NSUserDefault
Now this method is good if you want to keep your data live as long as the app is installed, once the app is removed this will reset automatically. You also have the option to update the value of the key if you wish, here is how you do NSUserDefault:
I always like to register my NSUserDeafult to default setting, a lot of people just continue with the second step without registering.
Register NSUserDefault in AppDelgate.swift
NSUserDefaults.standardUserDefaults().registerDefaults(["valueName": AnyObject])
Set Value to your NSUserDefault, this depends on what type of data you're storing, should match the one with your registration if you did register. (Example of Boolean data type below)
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "valueName") //Bool Data Type
Make sure you synchronize once you set the value to the NSUserDefault, this way it will update instantly, otherwise it will update when it get a chance.
NSUserDefaults.standardUserDefaults().synchronize()
Receive Value: this will receive boolean value since we set boolean and register boolean.
let Variable: Bool! = NSUserDefaults.standardUserDefaults().boolForKey("valueName")
Singleton
Now singleton is basically a global variable that you can use them in any views, but some developers experience some bugs and difficulties, use it at your own risk, I recommend this method when you're definite that you will use that data a lot (STILL RISKY), but this method is like goddess of data handling :).
Create a NSObject subclass and call it DataManager.swift (I call it data manager cause it handle data.) as following:
import UIKit
class DataManager: NSObject {
//Store Data Globally
static var someData: Boo! //This Boolean, you can choose whatever you want.
}
the static is what keep your data live.
Now you can store and receive someData from anywhere like you handle any data type like this.
//Store
DataManager.someData = true
//Receive
print(DataManager.someData)
Challenges:
You can also use
Keychain
Sergey Kargopolov will walk you through how to use a third party to use swift keychain. Otherwise, you can take even harder challenge and create one yourself :P .
Key-Value Data in iCloud
The best way to do this would be to pass it over when you do the prepare for segue method. So to do this make a variable in your detailPopViewController. In this case your pop over segue in storyboard will have the segue identifier detailView. Also tblSearchResults is your tableView outlet (you can name it whatever you want). Is that what you were looking for?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailView" {
if let detailViewController = segue.destinationViewController as? PopOverViewController{
if let indexPath = self.tblSearchResults.indexPathForCell(sender as! SOITTableViewController) {
detailViewController.objectInSecondVC = objectFromFirstVC[indexPath.row]
}
}
}
}
I'm having trouble wrapping my head around delegation in Swift. After reading some guides, I was able to set it up delegation between two ViewControllers, but I'm not understanding how it works. In my first view controller, I have a a label that displays what has been entered in the second view controller which contains a text field and a button (that returns to the first view controller). Here is the code for the first view controller:
#IBOutlet weak var labelText: UILabel!
func userDidEnterInformation(info: String) {
labelText.text = info;
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "transition"){
let secondVC: SecondViewController = segue.destinationViewController as! SecondViewController;
secondVC.delegate = self;
}
}
Here's the code for the second view controller:
protocol DataEnteredDelegate{
func userDidEnterInformation(info: String);
}
#IBOutlet weak var userText: UITextField!
var delegate: DataEnteredDelegate? = nil;
#IBAction func buttonPressed(sender: AnyObject) {
let information = userText.text!;
delegate!.userDidEnterInformation(information);
self.navigationController?.popToRootViewControllerAnimated(true);
}
My understanding is that in the text inside the text field gets stored in the information constant, then the userDidEnterInformation method from the protocol is called, with the method being defined inside the first view controller. This method then changes the label inside the first view controller. The thing is, I'm not sure what is happening in the prepareForSegue function. Specifically, I'm not sure what's the purpose of secondVC.delegate = self.
I would appreciate any sort of clarity on delegation.
The diagram is simple but can help you understand what's going on.
FirstViewController must conform to the DataEnteredDelegate protocol you have defined (see Sumit's answer). When using secondVC.delegate = self, you are saying that for the segue transition with the destination being a SecondViewController, the attribute delegate of that SecondViewController instance will be set to this instance of FirstViewController, thus delegating things from SecondViewController to your FirstViewController as made possible by the DataEnteredDelegate protocol.
The protocol you created in second viewcontroller is an Interface. You must implement your first view controller with the DataEnteredDelegate protocol.
class FirstViewController:UIViewController, DataEnteredDelegate{
func userDidEnterInformation(info: String) {
//stub
}
}
If the delegate of the second VC is not set in prepareForSegue() it remains nil. The second VC is then unable to call the first VC.
On a side note, if the delegate is nil your code will crash because delegate! is trying to unwrap an optional binding with the value of nil. It's better to first unwrap the delegate variable:
if let handler = delegate {
handler.userDidEnterInformation(information)
}
Alternatively, you could use Swift's Optional Chaining, calling userDidEnterInformation only if delegate is not nil.
delegate?.userDidEnterInformation(information);
In addition it is recommended to declare the delegate weak, to prevent retain cycles:
weak var delegate: DataEnteredDelegate?
Delegates and Protocols
Do not try to figure out how the dictionary definition of “delegate” fits with the concept of delegation in Swift. It doesn't.
Delegation in Swift is an agreement between two players—a sensing object and a requesting object. The “delegate” is the “requesting object.” Just think “asker” or “requester” every time you see “delegate” and it will make a lot more sense. Here is their agreement...
The Sensing Object (Second View Controller):
I have data from some event that took place. I will publish instructions (a protocol) on how you may access that data. If you want it, you must do three things.
You must declare in your class type that your class abides by my protocol.
You must write the functions that I describe in my protocol. I don't care what those functions do but the function type must match what I publish.
In YOUR code, you must set MY “delegate” (think “asker”) property to point to you. {secondVC.delegate = self} That way I can call one of YOUR functions to deliver the data.
After that, when I get some data, I will call one of the functions in your object that I told you to write. My call to your function will contain the data you are looking for as one of the arguments. {delegate!.userDidEnterInformation(information)} Note: delegate! (asker!) is YOU.
The Delegate (Requesting) Object (First View Controller):
O.K. You've got a deal.