Simulating Universal Gravitation with SpriteKit

Gravity in SpriteKit is a single planet sort of gravity. By that I mean that it applies a single force to all bodies in the simulation - basically everything falls down. But what if you wanted a mutliple planet sort of gravity, can that be achieved in SpriteKit?

The answer is yes, and it turns out it’s it doesn’t take a whole lot of code to get some fun and quite realistic results.

Screen capture. Small circular nodes orbit a larger central node like planets around a star.

In this SpriteKit app, dragging on the screen creates a new ‘planet’.

How it Works

First we turn off gravity as it normally applies in a SpriteKit scene and then on every tick we apply Newton’s law of universal gravitation to all the nodes in the physics simulation.

F=G m1.m2/r^2

That is for every pair of nodes, apply a force to each one that is equal to the product of their masses (and the universal gravitational constant), divided by the distance between them, squared.

There are some tweaks to the above formula to make the numbers a bit easier to deal with (i.e. smaller) and to make creating stable systems a bit easier, but sticking exactly to the formula above and plugging in some realistic numbers things work pretty much as you’d expect.

In addition to simulating gravity, I’m also combining planets that pass close to each other and adding trails to trace their paths and giving new planets random colour. It all results in a surprisingly fun and addictive little toy so even if you’re not interested in the code just build and run it on your iPhone (or watch, or Mac) and enjoy!

Get the code at github.com/mbehan/fgmmr.

Detecting Which Complication Launched Your WatchKit App

One of the joys of working with watchOS, much like it was working with iPhone OS many years ago, is the enforced simplicity. Free from worrying about about the unending device combinations and configurations and the unlistible features and extension points of modern iOS the constraints of a limited SDK focus your creativity. Simple, robust, yet still delightful interfaces flow from your fingertips, designers designs are readily translated to working product.

Sadly though, we’re not content for long. Just like in the early days of iPhone OS, you soon find yourself wanting to do just a tiny bit more than Apple has made available, and so focus and delight makes way to our more common friend, the ugly hack. Today’s feature that just couldn’t wait for a proper API is: detecting which watch face complication launched my app.

How It Works

When your app is launched in response to the user tapping a complication, the handleUserActivity method of your WKExtensionDelegate is called. You’re given a userInfo dictionary, and this is where we’d hope to find the details of which complication had launched us. Sadly though there’s no CLKComplicationFamilyKey to let you know the user tapped the circual small rather than the utilitarian large to lauch the app, but there is something we can use, the CLKLaunchedTimelineEntryDateKey. This gives us the exact date and time that the complication was created at. By remembering exactly when we created which complication then we can figure out which complication resulted in the app being launched and acting accordingly.

The Code

// 1.
class ComplicationTimeKeeper{
    
    static let shared = ComplicationTimeKeeper()
   
    var utilitarianLarge : Date?
    var utilitarianSmall : Date?
    var circularSmall : Date?
    var modularLarge : Date?
    var modularSmall : Date?
}

// 2. in your CLKComplicationDataSource
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping ((CLKComplicationTimelineEntry?) -> Void)) { 
     
     // Call the handler with the current timeline entry
     switch complication.family {
     case .utilitarianLarge:
            
         let date = Date()
         ComplicationTimeKeeper.shared.utilitarianLarge = date
            
         let template = CLKComplicationTemplateUtilitarianLargeFlat()
         template.textProvider = CLKSimpleTextProvider(text:"Something")
            
         let timelineEntry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
         handler(timelineEntry)
            
     default: handler(nil)
     }
}

// 3. in your WKExtensionDelegate
func handleUserActivity(_ userInfo: [AnyHashable : Any]?) {
    
    guard let userInfo = userInfo, let timelineDate = userInfo[CLKLaunchedTimelineEntryDateKey] as? Date else{
        return
    }
    
    if let utilLarge = ComplicationTimeKeeper.shared.utilitarianLarge, timelineDate.compare(utilLarge) == .orderedSame {
         WKExtension.shared().rootInterfaceController?.pushController(withName: "SomeController", context: nil)
    }
}

In 1, we create a singleton (no shameful hack is complete wihtout one) to track when our various complications were made.

In 2, we setup the utilitarian large complication and store the creation date, just add more cases to the switch statement for other complication families that you are supporting.

Finally in 3 we check what time the complication that launched the app was created and check which one it was and launch the relavant interface controller.

Limitations

The code above has a couple of limitations that you may need to work around. First it doesn’t take Time Travel into account so if your app supports that each complication may have more than one corresponding datetime. Secondly (though in practice I haven’t seen this be an issue) I don’t see why two complications couldn’t have clashing datetimes, for that you could add a method to ComplicationTimeKeeper that returns the next unique date.

It’s Time For Complications

Apple made much of the value of complications at this years WWDC. Having originally not allowed you to make your own in watchOS 1, to allowing you but telling you its only if you really have something super important that gets updates throughout the day in watchOS 2, now this year they told us we really need to have a complication even if its just an icon to launch your app. It seems they’ve noticed, as anyone who has worn Apple Watch for any reasonable amount of time will tell you, that complications are the best way to access the functionality of an app. But everything they talked about at WWDC was about having a complication, singular. You can support multiple complication families, but you can only have one of each and they are treated as different views of a single feature, showing more data when you’ve the room, but not really doing anything different.

Ideally, we’d have the ability to provide multiple complications for each complication family. If that was the case you could have a watch face with each complication slot filled by the same applicaiton, each showing something else (the built in world clock complication can already do this, but nothing else) and crucially each performing a different function of your app when they’re tapped. I wouldn’t be surprised if this is something that is eventually supported in WatchKit, but for now at least we can ugly hack our way to using different complication families to provide different functionality.

Should Apple Deprecate UILongPressGestureRecognizer?

The answer is yes.

  • For anywhere you currently require a long press, move to 3D touch.
  • For anywhere you have different actions for both, make the long press action an option when 3D touching. (For example organising icons on the home screen.)
  • Make an accesability preference that makes a long press behave as a progressivly more forcefull 3D touch.