In my Swift application, I instantiate PubNub on viewDidLoad with the
correct configuration and then join a channel. However, it looks as if it
doesn't join the channel at all unless I fire the Send button action to
send a message (I threw the config/join channel code in the Send button
just to see if it would work). The code in the Send button function is
identical to the code in the viewDidLoad, but the code in viewDidLoad is
still ignored. This is frustrating because the current user on my app won't
receive messages sent to the channel unless the user first sends a message.
Here's the code in the view controller (I removed a lot of the code so it's easier to look at. If you think the issue lies elsewhere I can post more):
class MessageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, PNObjectEventListener{
var pubNub: PubNub!
var pnConfiguration: PNConfiguration!
var ids: String = ""
#IBAction func sendButton(sender: UIButton) {
//create Pubnub channel
pnConfiguration = PNConfiguration(publishKey: "key", subscribeKey: "key")
pnConfiguration.uuid = currentUser.objectId!
print(pnConfiguration.uuid)
pubNub = PubNub.clientWithConfiguration(pnConfiguration)
let channelName = ids as String
let channelArray: [String] = [channelName]
pubNub.subscribeToChannels(channelArray, withPresence: true)
pubNub.publish(self.messageText.text!, toChannel: channelName, compressed: false, withCompletion: nil)
pubNub.addListener(self)
self.messageText.enabled = true
self.view.userInteractionEnabled = true
dispatch_async(dispatch_get_main_queue()) {
self.messageText.text = ""
self.messageTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Query that sets self.ids = user1.objectId! + user2.objectId!
//set Pubnub config
pnConfiguration = PNConfiguration(publishKey: "key", subscribeKey: "key")
pubNub = PubNub.clientWithConfiguration(pnConfiguration)
pnConfiguration.uuid = currentUser.objectId!
//create channel
let channelName = ids as String
print(channelName)
let channelArray: [String] = [channelName]
pubNub.subscribeToChannels(channelArray, withPresence: true)
pubNub.addListener(self)
dispatch_async(dispatch_get_main_queue()) {
self.messageTableView.reloadData()
}
}
EDIT:
After looking at the logs it looks like the reason why it's not working is because it's joining a channel called "", even though the print(channelName) returns the real channel name. Why isn't it taking the channel name? Is it not the right data type?
In your code snippet I don't see any place where you set real value for ids variable which you declare at line #7. So client use value which is stored in that variable: ""
I think this variable changed during controller life-cycle and set to proper value before you hit sendButton but not available at time when view is loaded.
Best regards,
Sergey
Related
I have a production-ready app where I need to run some code only on users with the previous versions installed but not on new installations. For instance, if 1.0 is the latest version in the AppStore and 2.0 is the new one that will introduce code that needs to run only on users with version 1.0 but not on new users.
e.g.
if isExistingUser{
// run code
}
What would be the best way to run some code only for existing users? How can one determine whether it's a new or existing user?
Does your app create any data? Maybe files in the Documents directory, or maybe a UserDefaults key? If so, check for the presence of one of those things very early in launch, which will signal to you that this must be an upgrade and you should do your thing.
A lot of people store the app's CFBundleShortVersionString Info.plist key into UserDefaults at launch, which makes it easy to know the last version that was run and let you write the logic of what needs to happen to migrate from that version to the new version. This is something you might want to think about doing in future versions.
I see this often and knowing how to do this can be extremely valuable, especially when doing something like introducing a new feature that changes the experience your previous users had.
There are a few approaches you can take depending on your needs.
Firstly, you could create a boolean variable in your user model class that is set during user registration in the standard user registration flow and indicates that this is a newly created user - and name it something like isNewOnVersionTwo which will indicate this user is a new user on this new version.
Code Example:
class User: Decodable {
var uid: string!
var username: string!
var isNewOnVersionTwo: Bool = false
}
class RegistrationViewController: UIViewController {
var user: User!
var isNewOnVersionTwo: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
user.isNewOnVersionTwo = true
}
}
class HomeViewController: UIViewController {
var user: User!
override func viewDidLoad() {
super.viewDidLoad()
isNewOnVersionTwo == true ? normalInit() : showOldUserAView()
}
func normalInit() {
// Run normal functions for the 'HomeViewController' or do nothing.
}
func showOldUserAView() {
//Do something specific for only old users.
}
}
You can choose whether you want to hold onto this variable permanently or not - it could be useful for tracking the new users you've gained on this version versus the previous versions - and you could send it to your database along with the rest of the data from your user model.
A second and cleaner approach...
Could be to only set the boolean on the very last view controller of the registration flow and pass it to the home view controller when you push the user to the view controller.
Like this:
class ViewControllerOne: UIViewController {
var isNewOnVersionTwo: Bool = false
private func pushToNewViewController() {
let vc = HomeViewController()
vc.isNewOnVersionTwo = true
navigationController?.pushViewController(vc, animated: true)
}
}
class HomeViewController: UIViewController {
var isNewOnVersionTwo: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
isNewOnVersionTwo == true ? normalInit() : showOldUserAView()
}
func normalInit() {
// Run normal functions for the 'HomeViewController' or do nothing.
}
func showOldUserAView() {
//Do something specific for only old users.
}
}
I wouldn't take the approach of using UserDefaults for the few following reasons:
1. Devices occasionally wipe this randomly and it's not as reliable as hardcoding a flag in the source code.
2. UserDefaults isn't available for a few moments on cold app launches / initial startup which can complicate things, varying on when you want to show the older users whatever you want to show them.
Here is what I came up with that I think works for 95-98% of the users. What I'm doing is basically comparing the date when the Documents folder was created and the date when version2 will be released. The only issue I see with this method is for users installing the app between the date you specified as the release date and the actual App Store release date. In other words, if you specify a date of June, 5 as the release date but the app isn't really approved until the 7th, users who installed the app on the 6th will be missed.
Ideally and it's what I will start doing is what #Rudedog suggested, basically saving the versions to keep track of what version a user has.
/// - Returns: Returns true if it's a new user otherwise returns false.
func isExistingUser()->Bool{
var isExistingUser:Bool?
var appInstallationDate:Date?
/// Set the date when the version two is released.
var dateComponents = DateComponents()
dateComponents.year = 2022
dateComponents.month = 06
dateComponents.day = 4
let userCalendar = Calendar.current
let versionsTwoReleaseDate = userCalendar.date(from: dateComponents)
/// Get the date of when the documents folder was created to determine when the app was installed.
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
if let creationDate = (try? documentsDirectory!.resourceValues(forKeys: [.creationDateKey]))?.creationDate{
appInstallationDate = creationDate
}
/// Compare dates
if appInstallationDate! < versionsTwoReleaseDate!{
// print("It's existing user")
isExistingUser = true
}else{
// print("It's NEW user")
isExistingUser = false
}
return isExistingUser!
}
This is my first time using Uber api. I followed the instructions clearly, but it never really mentioned how to display price estimate in the button. My code magically displays the time( dunno why or how). Please explain how to display price as well. Both server token and client ID have been integrated in the info.plist file.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = RideRequestButton()
view.addSubview(button)
button.center = view.center
let ridesClient = RidesClient()
let dropOffLocation = CLLocation(latitude: 20.301647, longitude: 85.819135)
let pickUpLocation = CLLocation(latitude : 20.323706, longitude: 85.814981)
let builder = RideParametersBuilder()
builder.pickupLocation = pickUpLocation
builder.pickupNickname = "Home"
builder.dropoffLocation = dropOffLocation
builder.dropoffNickname = "Mayfair Lagoon, Bhubaneswar"
var productID = ""
ridesClient.fetchProducts(pickupLocation: pickUpLocation) { (product, response) in
productID = product[1].productID
print("🥒\(productID)")
}
ridesClient.fetchPriceEstimates(pickupLocation: pickUpLocation, dropoffLocation: dropOffLocation) { (price, response) in
print(price[0].estimate!,"🍚")
}
ridesClient.fetchTimeEstimates(pickupLocation: pickUpLocation) { (time, response) in
print("🥕",time[0].estimate,"🥕")
}
builder.productID = productID
button.setContent()
button.rideParameters = builder.build()
button.loadRideInformation()
}
}
Button will Deeplink into the Uber App and will simply open up the app. In order to see real-time fare estimates and pickup ETA information you will need to pass additional parameters to it. The Ride Request Button can accept optional parameters to pre-load some information into the ride request. You can see how to do it in the Uber documentation. Also this is explained here on the GitHub
Please, check out StackOverflow thread here.
It is explained and documented how to manage this issue.
Please check it out below link
Display ETA and estimated money option for my "Ride there with Uber" button
I have this code in Swift 3 to get backendless user so I can get his/her properties:
let whereClause = "objectId = '\(userId)'"
let query = BackendlessDataQuery()
query.whereClause = whereClause
let data = Backendless.sharedInstance().persistenceService.of(BackendlessUser.ofClass())
data?.find(query, response: { (result) in
let user = result?.data.first as! BackendlessUser
myLabel.text = user.getProperty("firstName") as! String
})
The code is working fine but my question is how to observe the property changes ? is there a way if the value of property firstName changed I can update my label automatically ?
The use-case you describe is not really as simple as it may seem, but it's definitely possible.
You can capture any changes in Users table by creating an afterUpdate event handler. From there you could publish a message to some dedicated channel in messaging service.
At the same time, your application should be subscribed to this same channel. This way you could update your UI when the appropriate message is received.
Ok, I am new to URL querying and this whole aspect of Swift and need help. As is, I have an iMessage app that contains and SKScene. For the users to take turns playing the game, I need to send the game back and forth in messages within 1 session as I learned here : https://medium.com/lost-bananas/building-an-interactive-imessage-application-for-ios-10-in-swift-7da4a18bdeed.
So far I have my scene all working however Ive poured over Apple's ice cream demo where they send the continuously-built ice cream back and forth, and I cant understand how to "query" everything in my SKScene so I can send the scene.
I'm unclear as to how URLQueryItems work as the documentation does not relate to sprite kit scenes.
Apple queries their "ice cream" in its current state like this:
init?(queryItems: [URLQueryItem]) {
var base: Base?
var scoops: Scoops?
var topping: Topping?
for queryItem in queryItems {
guard let value = queryItem.value else { continue }
if let decodedPart = Base(rawValue: value), queryItem.name == Base.queryItemKey {
base = decodedPart
}
if let decodedPart = Scoops(rawValue: value), queryItem.name == Scoops.queryItemKey {
scoops = decodedPart
}
if let decodedPart = Topping(rawValue: value), queryItem.name == Topping.queryItemKey {
topping = decodedPart
}
}
guard let decodedBase = base else { return nil }
self.base = decodedBase
self.scoops = scoops
self.topping = topping
}
}
fileprivate func composeMessage(with iceCream: IceCream, caption: String, session: MSSession? = nil) -> MSMessage {
var components = URLComponents()
components.queryItems = iceCream.queryItems
let layout = MSMessageTemplateLayout()
layout.image = iceCream.renderSticker(opaque: true)
layout.caption = caption
let message = MSMessage(session: session ?? MSSession())
message.url = components.url!
message.layout = layout
return message
}
}
But I cant find out how to "query" an SKScene. How can I "send" an SKScene back and forth? Is this possible?
You do not need to send an SKScene back and forth :) What you need to do is send the information relating to your game set up - such as number of turns, or whose turn it is, or whatever, as information that can be accessed by your app at the other end to build the scene.
Without knowing more about how your scene is set up and how it interacts with the information received for the other player's session, I can't tell you a lot in terms of specifics. But, what you need to do, if you are using URLQueryItems to pass the information, simply retrieve the list of query items in your scene and set up the scene based on the received values.
If you have specific questions about how this could be done, if you either share the full project, or post the relevant bits of code as to where you send out a message from one player and how the other player receives the information and sets up the scene, I (or somebody else) should be able to help.
Also, if you look at composeMessage in the code you posted above, you will see how in that particular code example the scene/game information was being sent to the other user. At the other end of the process, the received message's URL parameter would be decomposed to get the values for the various query items and then the scene would be set up based on those values. Look at how that is done in order to figure out how your scene should be set up.
I have seen similar posts on this, but nothing that is helping me out. I can not for the life of me determine why I can't extract data from my task. I am trying to update an object (myUser) with data obtained from a SOAP XML response from a Web Service I built. I am new to Swift and IOS, but I've done this with C#. I can update the label on the viewcontroller with the results from the web service, but not a class variable. Thanks in advance for your help!
class FirstViewController: UIViewController {
#IBOutlet weak var label: UILabel! //CAN UPDATE THIS FROM TASK
var myUser = User() //CAN NOT UPDATE THIS FROM TASK
func soapRequest(){
...
let task = session.dataTaskWithRequest(request) {(data, response, error)-> Void in
do{ xmlResponse = try AEXMLDocument(xmlData: data!)}
catch{print("\(error)")}
//Example of the data I am trying to send to the ViewController object
let firstName = xmlResponse.root["element1"].stringValue
//I've tried this
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.myUser.firstName = firstName //This does not work
self.label.text = firstName //This DOES work
})
}
task.resume()
}
}
Dan's comment was the correct solution to my issue. The code was executing correctly, but since the request is asynchronous, it wasn't finishing in time and I assumed it was broken.
Your network request is asynchronous, it won't have finished yet when
you print the value in viewDidLoad so the value won't have been set
yet. – dan 1 hour ago