SKScene and URLQueryItems in Swift3? - ios

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.

Related

iOS - PhotosKit - Troubles identifying modified assets

I'm working with the Photos framework, specifically I'd like to keep track of the current camera roll status, thus updating it every time assets are added, deleted or modified (mainly when a picture is edited by the user - e.g a filter is added, image is cropped).
My first implementation would look something like the following:
private var lastAssetFetchResult : PHFetchResult<PHAsset>?
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let fetchResult = lastAssetFetchResult,
let details = changeInstance.changeDetails(for: fetchResult) else {return}
let modified = details.changedObjects
let removed = details.removedObjects
let added = details.insertedObjects
// update fetch result
lastAssetFetchResult = details.fetchResultAfterChanges
// do stuff with modified, removed, added
}
However, I soon found out that details.changedObjects would not contain only the assets that have been modified by the user, so I moved to the following implementation:
let modified = modifiedAssets(changeInstance: changeInstance)
with:
func modifiedAssets(changeInstance: PHChange) -> [PHAsset] {
var modified : [PHAsset] = []
lastAssetFetchResult?.enumerateObjects({ (obj, _, _) in
if let detail = changeInstance.changeDetails(for: obj) {
if detail.assetContentChanged {
if let updatedObj = detail.objectAfterChanges {
modified.append(updatedObj)
}
}
}
})
return modified
}
So, relying on the PHObjectChangeDetails.assetContentChanged
property, which, as documentation states indicates whether the asset’s photo or video content has changed.
This brought the results closer to the ones I was expecting, but still, I'm not entirely understanding its behavior.
On some devices (e.g. iPad Mini 3) I get the expected result (assetContentChanged = true) in all the cases that I tested, whereas on others (e.g. iPhone 6s Plus, iPhone 7) it's hardly ever matching my expectation (assetContentChanged is false even for assets that I cropped or added filters to).
All the devices share the latest iOS 11.2 version.
Am I getting anything wrong?
Do you think I could achieve my goal some other way?
Thank you in advance.

how to observe property changes of Backendless database ? - Swift

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.

Problems with structs in Swift and making a UIImage from url?

Alright, I am not familiar with structs or the ordeal I am dealing with in Swift, but what I need to do is create an iMessage in my iMessage app extension with a sticker in it, meaning the image part of the iMessage is set to the sticker.
I have pored over Apple's docs and https://www.captechconsulting.com/blogs/ios-10-imessages-sdk-creating-an-imessages-extension but I do not understand how to do this or really how structs work. I read up on structs but that has not helped me accomplishing what Apple does in their sample code (downloadable at Apple)
What Apple does is they first compose a message, which I understood, taking their struct as a property, but I take sticker instead
guard let conversation = activeConversation else { fatalError("Expected a conversation") }
//Create a new message with the same session as any currently selected message.
let message = composeMessage(with: MSSticker, caption: "sup", session: conversation.selectedMessage?.session)
// Add the message to the conversation.
conversation.insert(message) { error in
if let error = error {
print(error)
}
}
They then do this (this is directly from sample code) to compose the message:
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
}
}
Basically this line is what Im having the problem with as I need to set my sticker as the image:
layout.image = iceCream.renderSticker(opaque: true)
Apple does a whole complicated function thing that I don't understand in renderSticker to pull the image part out of their stickers, and I have tried their way but I think this is better:
let img = UIImage(contentsOfURL: square.imageFileURL)
layout.image = ing
layout.image needs a UIImage, and I can get the imageFileURL from the sticker, I just cant get this into a UIImage. I get an error it does not match available overloads.
What can I do here? How can I insert the image from my sticker into a message? How can I get an image from its imageFileURL?
I'm not sure what exactly the question is, but I'll try to address as much as I can --
As rmaddy mentioned, if you want to create an image given a file location, simply use the UIImage constructor he specified.
As far as sending just a sticker (which you asked about in the comments on rmaddy's answer), you can insert just a sticker into an iMessage conversation. This functionality is available as part of an MSConversation. Here is a link to the documentation:
https://developer.apple.com/reference/messages/msconversation/1648187-insert
The active conversation can be accessed from your MSMessagesAppViewController.
There is no init(contentsOfURL:) initializer for UIImage. The closest one is init(contentsOfFile:).
To use that one with your file URL you can do:
let img = UIImage(contentsOfFile: square.imageFileURL.path)

Can't receive PubNub message unless I publish a message first

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

iOS Game Center Combined Leaderboard Loading

Question:
When loading scores from a combined leaderboard, how do I tell which source leaderboard the score originated from?
Background:
I am new to using iOS Game Center. I currently have three leaderboards; let's call them "level1", "level2" and a combined one called "aggregate". I submit scores like this, and it seems to work:
let score = GKScore(leaderboardIdentifier: "level1", player: localPlayer)
score.value = // Score here...
score.context = // Context here...
GKScore.reportScores([gcScore]) { error in
// Error handling...
}
When I query to find a list of the top ten scores on all levels, I do this:
var leaderboard = GKLeaderboard()
leaderboard.identifier = "aggregate"
leaderboard.range = NSRange(location: 1, length: 10)
leaderboard.loadScoresWithCompletionHandler() { objects, error in
// Error handling removed...
let scores = objects as? [GKScore]
// Examine the scores, looking at each score.leaderboardIdentifier to
// determine where it originally came from.
}
Unfortunately, the leaderboardIdentifier on each score has the value "aggregate". I'm not sure if I'm doing something wrong or if I'm missing something, but my understanding of the leaderboard documentation makes it sound like this should be set to the original leaderboard value.
Any thoughts?

Resources