 <p>I have identified a workaround. </p> <p>I'm using a tuple for the constraint and handling it myself in the didApplyConstraints handler. </p> <p>The format for the custom constraint assumes that the range begins at the first value and travels <em>counter clockwise</em> until the second value. It makes no allowances for constraint values above PI or below -PI. This solution also doesn't deal with total ranges greater than 2*PI. </p> <p>Ranges that do not cross the +/- PI boundary are "normal" and can be handled normally.</p> <p>Ranges that DO cross the +/- PI boundary are "goofy" and must be handled as a special case (if possible). </p> <p>A solution to the special case seems impossible unless you are tracking angular velocity with an SKPhysicsBody (or manually). This value is required to identify which boundary the node likely "crossed" and should thus be constrained to.</p> <pre><code>// constraint = (CGFloat(M_PI/2), -CGFloat(M_PI/2)) override func didApplyConstraints() { var r = rotationSprite.zRotation if constraint.0 &lt; constraint.1 { // regular constraint, easy to handle. rotationSprite.zRotation = min(max(r, constraint.0),constraint.1) } else // "goofy" constraint that crosses PI boundary { if r &lt; constraint.0 &amp;&amp; r &gt; constraint.1 { if rotationSprite.physicsBody?.angularVelocity &lt; 0 { // clockwise, crossed first value (so long as absolute angular vel is &lt; 2PI rotationSprite.zRotation = constraint.0 } else if rotationSprite.physicsBody?.angularVelocity &gt; 0 // counter clockwise, crossed second value (so long as absolute angular vel is &lt; 2PI { rotationSprite.zRotation = constraint.1 } else { // If 0 velocity (or no velocity), no way to find out which boundary was crossed. rotationSprite.zRotation = constraint.0 // Probably better to default to closest angle } rotationSprite.physicsBody?.angularVelocity = 0 // Alternately, multiply by a negative restitution factor. } } } </code></pre>