I'm executing the SKAction rotateToAngle on one method. On another method I want to know in whether the action is still being executed or if it has ended.
I could save the time the action started in a property and check it however I was wondering whether there is an easier method.
private func rotate(motionManager: CMMotionManager, gravity: CGVector) {
let rotate = SKAction.rotateToAngle(CGFloat(M_PI * 5), duration: 5.0, shortestUnitArc: true)
self.runAction(rotate)
}
private func actionRunning() --> Bool {
}
I tried using self.hasActions() but it always return true. Any ideas on how to do this on Swift?
You could always use the withKey parameter when using runAction. Then do a actionForKey: method to determine if it still exists.
In Objective-C it looks like this:
[self runAction:[SKAction waitForDuration:0.1] withKey:#"waitTimer"];
[self actionForKey:#"waitTimer"];
I'm not familiar with Swift, but the Apple Docs do show it supported for the Swift Language as well.
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKNode_Ref/index.html#//apple_ref/occ/instm/SKNode/runAction:withKey:
func runAction(_ action: SKAction!,
withKey key: String!)
Related
I have 2 delegate methods that are being called by notifications from a 3rd party library.
Method 1:
mediaContentWasUpdated()
Method 2:
adMediaDidBeginPlaying()
In Method 1, a key variable (adDuration) is set from a parameter that is passed in with the notification. As far as I can see this is the only place to get this information.
In Method 2, we check the adDuration and if it is greater than 0 then we update the UI to reflect that we are in fact play an ad.
A bug has appeared where sometimes these two methods are called in the wrong order. Meaning the adDuration is not set and Method 2 thinks there is no ad media to be played and does not update the UI accordingly.
My current attempt at a solution is to make adDuration optional and use an NSCondition to cause Method 2 to wait for Method 1 to set adDuration and then proceed.
var adDuration : Double?
let condition = NSCondition()
func mediaContentWasUpdated(notification: NSNotificiation) {
condition.lock()
if(notificationHasAdDurationInfo(notification)) {
self.adDuration = getAdDuration(notification)
condition.signal()
}
condition.unlock()
}
func adMediaDidBeginPlaying(notification: NSNotification) {
condition.lock()
while adDuration == nil {
condition.wait()
}
if adDuration! > Double(0) {
updateUIForAd()
}
condition.unlock()
}
This is my first time trying something like this and I worry I am doing something wrong. I also have some concerns about locking and unlocking threads needlessly (which would happen in a well timed run, or if there were no ad content to be played).
Outside factors are hindering my ability to test and I wanted to get some input to see if I am heading in the right direction while I wait for those issues to be resolved.
Your discussion of NSCondition got me on the same track with you, and I built two or three solutions using DispatchGroup (which is the better tool for this), but they always had little corner cases that could behave badly, and didn't really capture the intent.
(If you're interested in the DispatchGroup solutions, they're of the form: call .enter() in init, call .leave() when the duration comes in, call notify() when the playing starts. It works fine, but it introduces corner cases that can crash, just like NSCondition.)
Getting back to the real intent:
Update the UI when the duration is known and the ad has started playing.
There's no concurrency going on here. So pulling out GCD is not just overkill; it actually makes things worse because it introduces lots of complicated corner cases.
So I thought about how I'd have solved this back before GCD. And the answer is obvious: just check if you have the data you want, and then do the thing. (Reading through the comments, I see Paulw11 pointed this out as well.)
Personally I like to pull this kind of thing into its own type to make things more self-contained. I hate some of the names here, but the idea should be clear:
class AdPlayer {
private var readyToPlay = false
private var duration: Double = 0.0
private let completion: (Double) -> Void
func setDuration(from notification: Notification) {
if(notificationHasAdDurationInfo(notification)) {
duration = getAdDuration(notification)
}
playIfReady()
}
func play() {
readyToPlay = true
playIfReady()
}
private func playIfReady() {
if duration > 0 && readyToPlay {
completion(duration)
}
}
init(completion: #escaping (Double) -> Void) {
self.completion = completion
}
}
When you set each thing, see if you're ready to update, and if so, update. I've gotten rid of the optional as well, since I believe the intent is "0 duration is always wrong." But you could use an Optional so you could detect actually receiving a 0 from the notification.
With that, you just set up a player property:
player = AdPlayer(completion: updateUIForAd)
(Note that the above might be creating a retain loop, depending on what updateUIForAd is; you may need a [weak self] closure or the like here.)
And then update it as needed:
func mediaContentWasUpdated(notification: NSNotificiation) {
player.setDuration(from: notification)
}
func adMediaDidBeginPlaying(notification: NSNotification) {
player.play()
}
A big advantage of creating the AdPlayer type is that it's easy to reset the system when the ad is done (or if something goes wrong). Just throw away the whole object and create another one.
I have many SKActions in a SpriteKit project. The default timingMode for SKActions is "linear". Is it possible to use an extension to override this timingMode default to e.g. "easeInEaseOut" so ALL SKActions have timingMode = easeInEaseOut?
I have tried various "extension" styles but none will compile - normally returning "'timingMode' used within its own type" or "Initializer 'init()' with Objective-C selector 'init' conflicts with implicit initializer 'init()' with the same Objective-C selector"
The docs don't seem to give any examples of this, but surely this would be a useful thing to be able to do? Especially when you have hundreds of SKActions in your game?
Pick your poison, one extends the action to allow you to quickly call .easeInEaseOut timing mode, the other extends SKNode to allow you to run using a specific timing mode.
There is no way to change default behavior, the only other way is to create your own static methods for every action that exists, which can become cumbersome.
extension SKAction
{
//e.g. SKAction.move(to: CGPoint.zero, duration: 10).easeInEaseOut()
func easeInEaseOut() -> SKAction
{
self.timingMode = .easeInEaseOut
return self
}
}
extension SKNode
{
func runWithEaseInEaseOut(action:SKAction,withKey key: String = "")
{
action.timingMode = .easeInEaseOut
if key != ""
{
self.run(action,withKey:key)
}
else
{
self.run(action)
}
}
}
I am working on a project that includes an annotation tool allowing users to "draw" on documents with finger gestures or a pencil. Naturally, I'm keen on implementing undo/redo for drawn paths.
My implementation for the drawing app is relatively conventional. What the user sees on the screen is the combination of a cached bitmap image (a snapshot of all the paths that have been drawn before the current one) together with a "live" rendering of the current path (a UIBezierPath). When touchesEnded is triggered, the new path is added to the bitmap.
I have been able to implement undo with relatively little trouble. I have created a standard undoManager for the class:
let myUndoManager : UndoManager = {
let mUM : UndoManager = UndoManager()
mUM.levelsOfUndo = 6
return mUM
}()
The function that is called at the end of touchesEnded to render the new cached path is called drawBitmap. At the start of this function, assuming there is a previous cached path and before drawing the new one, I register the following undo action with the undo manager:
let previousCachedPath : UIImage = self.cachedPath
self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: previousCachedPath)
setBitmap(_ previousCachedPath : UIImage) is a function that resets the displayed bitmap to the provided image.
I have a undo/redo buttons linked to undo() and redo() methods respectively. Aside from some logic dictating when these buttons should be active (i.e. to make sure you can't press undo when nothing has been drawn etc.), these simply call myUndoManager.undo() and myUndoManager.redo() respectively:
func undo() -> Void {
guard self.myUndoManager.canUndo else { return }
self.myUndoManager.undo()
if !self.redoButton.isEnabled {
self.redoButton.isEnabled = true
}
if !self.myUndoManager.canUndo {
self.undoButton.isEnabled = false
}
self.setNeedsDisplay()
}
func redo() -> Void {
guard self.myUndoManager.canRedo else { return }
self.myUndoManager.redo()
if !self.undoButton.isEnabled {
self.undoButton.isEnabled = true
}
if !self.myUndoManager.canRedo {
self.redoButton.isEnabled = false
}
self.setNeedsDisplay()
}
As I mentioned, undo works perfectly to the specified six levels of undoability. However, I'm clearly missing something with redo. My initial hope was that the undoManager would automatically transfer undo tasks from the undo stack to the redo stack when undo is called, but this is clearly not happening.
I have already searched for answers, and I think the closest to what I need may to use mutual recursion as per:
Using NSUndoManager, how to register undos using Swift closures
However, I have been unable to make this work. Any help therefore appreciated!
Thanks to #matt 's help, I solved this by putting everything in the setBitmap(_:) function. To try and better understand things, I implemented both the registerUndo(withTarget:selector:) approach:
func setBitmap(_ toCachedPath : UIImage) -> Void {
self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: self.cachedPath)
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
toCachedPath.draw(at: CGPoint.zero)
self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
And also with the prepare(withInvocationTarget:) approach:
func setBitmap(_ toCachedPath : UIImage) -> Void {
if self.cachedPath != nil {
(self.rWUndoManager.prepare(withInvocationTarget: self) as AnyObject).setBitmap(self.cachedPath)
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
toCachedPath.draw(at: CGPoint.zero)
self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
Hope that helps anybody else who were scratching their heads as much as I was.
Actually you could use the closure version registerUndo(withTarget:handler:) for redo to work too. Just make sure it would work with the selector one registerUndo(withTarget:selector:object:) first, i.e. make a function which takes only one parameter, like in your answer. And then you can replace the selector-based method with the closure one:
func setBitmap(_ toCachedPath : UIImage) -> Void {
let oldCachedPath = cachedPath // For not referencing it with `self` in the closure.
myUndoManager.registerUndo(withTarget: self) {
$0.setBitmap(oldCachedPath)
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
toCachedPath.draw(at: CGPoint.zero)
self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
I guess the closure version is just a repackage of the selector version which recognises only one method and one input parameter, so we still have to write like this for it to work.
I want to write a function to reverse geocode a location and assign the resulting string into a variable. Following this post i've got something like this:
extension CLLocation {
func reverseGeocodeLocation(completion: (answer: String?) -> Void) {
CLGeocoder().reverseGeocodeLocation(self) {
if let error = $1 {
print("[ERROR] \(error.localizedDescription)")
return
}
if let a = $0?.last {
guard let streetName = a.thoroughfare,
let postal = a.postalCode,
let city = a.locality else { return }
completion(answer: "[\(streetName), \(postal) \(city)]")
}
}
}
}
For calling this function i've just got something like this:
location.reverseGeocodeLocation { answer in
print(answer)
}
But instead i want to assign the string value of answer to a variable and i don't know how to pass that data out of the closure. What is the best way to do something like this?
The problem is that it runs asynchronously, so you can't return the value. If you want to update some property or variable, the right place to do that is in the closure you provide to the method, for example:
var geocodeString: String?
location.reverseGeocodeLocation { answer in
geocodeString = answer
// and trigger whatever UI or model update you want here
}
// but not here
The entire purpose of the closure completion handler pattern is that is the preferred way to provide the data that was retrieved asynchronously.
Short answer: You can't. That's not how async programming works. The function reverseGeocodeLocation returns immediately, before the answer is available. At some point in the future the geocode result becomes available, and when that happens the code in your closure gets called. THAT is when you do something with your answer. You could write the closure to install the answer in a label, update a table view, or some other behavior. (I don't remember if the geocoding methods' closures get called on the main thread or a background thread. If they get called on a background thread then you would need to wrap your UI calls in dispatch_async(dispatch_get_main_queue()).)
I have the following code:
func setupShortcutItems(launchOptions: [NSObject: AnyObject]?) -> Bool {
var shouldPerformAdditionalDelegateHandling: Bool = false
if (UIApplicationShortcutItem.respondsToSelector("new")) {
self.configDynamicShortcutItems()
// If a shortcut was launched, display its information and take the appropriate action
if let shortcutItem: UIApplicationShortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
// When the app launched at the first time, this block can not called.
self.handleShortCutItem(shortcutItem)
// This will block "performActionForShortcutItem:completionHandler" from being called.
shouldPerformAdditionalDelegateHandling = false
} else {
// normal app launch process without quick action
self.launchWithoutQuickAction()
}
} else {
// Less than iOS9 or later
self.launchWithoutQuickAction()
}
return shouldPerformAdditionalDelegateHandling
}
I get the following "warning" on UIApplicationShortcutItem.respondsToSelector("new"), which says:
Use of string literal for Objective-c selectors is deprecated, use '#selector' instead
The warning replaces the code automatically with:
UIApplicationShortcutItem.respondsToSelector(#selector(FBSDKAccessToken.new))
However this doesn't compile because new() is unavailabe.
What am I supposed to use in this case?
Xcode 7.3 using swift for iOS9.3/watchOS2.2/...
If you previously used this line of code:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateResult:", name: "updateResult", object: nil)
you should now use this line of code:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(InterfaceController.updateResult(_:)), name: "updateResult", object: nil)
at least this is what Xcode offered me after I changed around a few characters in the code. Seems like it doesn't always offer the correct solution when you are presented with this error.
Create a protocol whose only reason for existing is to allow you to construct the appropriate selector. In this case:
#objc protocol NewMenuItemHandling {
func new()
}
You are taking the informal protocol (an object that responds to the new selector) and making it into a formal protocol.
Then where you want to use the selector you can add the expression:
#selector(NewMenuItemHandling.new)
In this special respondsToSelector situation, where you have no existing method with which to associate a function reference, write this:
UIApplicationShortcutItem.respondsToSelector(Selector("new"))
You'll still get a warning (you shouldn't, and I've filed a bug against it), but it will be a different warning and you can ignore it.
In summary, every "selector: function or object" is now "selector: #selector(class.funtion(_:))" wherever used.