JoyStickView

@IBDesignable
public final class JoyStickView : UIView
extension JoyStickView: UIGestureRecognizerDelegate

A simple implementation of a joystick interface like those found on classic arcade games. This implementation detects and reports two values when the joystick moves:

  • angle: the direction the handle is pointing. Unit is degrees with 0° pointing up (north), and 90° pointing right (east).
  • displacement: how far from the view center the joystick is moved in the above direction. It has no units as it is the ratio of distance moved from center over the radius of the joystick base. Always in range 0.0-1.0.

The view has several settable parameters that be used to configure a joystick’s appearance and behavior:

  • monitor: an enumeration of type JoyStickViewMonitorKind that can hold a function to receive updates when the joystick’s angle and/or displacement values change. Supports polar and cartesian (XY) reporting
  • movable: a boolean that when true lets the joystick move around in its parent’s view when there joystick moves beyond displacement of 1.0.
  • movableBounds: a CGRect which limits where a movable joystick may travel
  • baseImage: a UIImage to use for the joystick’s base
  • handleImage: a UIImage to use for the joystick’s handle

Additional documentation is available via the attribute names below.

  • Optional monitor which will receive updates as the joystick position changes. Supports polar and cartesian reporting. The function to call with a position report is held in the enumeration value.

    Declaration

    Swift

    public var monitor: JoyStickViewMonitorKind
  • Optional block to be called upon a tap.

    Declaration

    Swift

    public var tappedBlock: (() -> Void)? { get set }
  • Optional rectangular region that restricts where the handle may move. The region should be defined in this view’s coordinates. For instance, to constrain the handle in the Y direction with a UIView of size 100x100, use CGRect(x: 50, y: 0, width: 1, height: 100)

    Declaration

    Swift

    public var handleConstraint: CGRect? { get set }
  • The last-reported angle from the joystick handle. Unit is degrees, with 0° up (north) and 90° right (east). Note that this assumes that angleRadians was calculated with atan2(dx, dy) and that dy is positive when pointing down.

    Declaration

    Swift

    public var angle: CGFloat { get }
  • The last-reported displacement from the joystick handle. Dimensionless, it is the ratio of movement over the radius of the joystick base. Always falls between 0.0 and 1.0

    Declaration

    Swift

    public private(set) var displacement: CGFloat { get }
  • If true the joystick will move around in the parent’s view so that the joystick handle is always at a displacement of 1.0. This is the default mode of operation. Setting to false will keep the view fixed.

    Declaration

    Swift

    @IBInspectable
    public var movable: Bool
  • The original location of a movable joystick. Used to restore its position when user double-taps on it.

    Declaration

    Swift

    public var movableCenter: CGPoint?
  • Optional rectangular region that restricts where the base may move. The region should be defined in the this view’s coordinates.

    Declaration

    Swift

    public var movableBounds: CGRect? { get set }
  • The opacity of the base of the joystick. Note that this is different than the view’s overall opacity setting. The end result will be a base image with an opacity of baseAlpha * view.alpha

    Declaration

    Swift

    @IBInspectable
    public var baseAlpha: CGFloat { get set }
  • The opacity of the handle of the joystick. Note that this is different than the view’s overall opacity setting. The end result will be a handle image with an opacity of handleAlpha * view.alpha

    Declaration

    Swift

    @IBInspectable
    public var handleAlpha: CGFloat { get set }
  • The tintColor to apply to the handle. Changing it while joystick is visible will update the handle image.

    Declaration

    Swift

    @IBInspectable
    public var handleTintColor: UIColor? { get set }
  • Scaling factor to apply to the joystick handle. A value of 1.0 will result in no scaling of the image, however the default value is 0.85 due to historical reasons.

    Declaration

    Swift

    @IBInspectable
    public var handleSizeRatio: CGFloat { get set }
  • Control how the handle image is generated. When this is false (default), a CIFilter will be used to tint the handle image with the handleTintColor. This results in a monochrome image of just one color, but with lighter and darker areas depending on the original image. When this is true, the handle image is just used as a mask, and all pixels with an alpha = 1.0 will be colored with the handleTintColor value.

    Declaration

    Swift

    @IBInspectable
    public var colorFillHandleImage: Bool { get set }
  • Controls how far the handle can travel along the radius of the base. A value of 1.0 (default) will let the handle travel the full radius, with maximum travel leaving the center of the handle lying on the circumference of the base. A value greater than 1.0 will let the handle travel beyond the circumference of the base, while a value less than 1.0 will reduce the travel to values within the circumference. Note that regardless of this value, handle movements will always report displacement values between 0.0 and 1.0 inclusive.

    Declaration

    Swift

    @IBInspectable
    public var travel: CGFloat
  • The image to use for the base of the joystick

    Declaration

    Swift

    @IBInspectable
    public var baseImage: UIImage? { get set }
  • The image to use for the joystick handle

    Declaration

    Swift

    @IBInspectable
    public var handleImage: UIImage? { get set }
  • Control whether view will recognize a double-tap gesture and move the joystick base to its original location when it happens. Note that this is only useful if moveable is true.

    Declaration

    Swift

    @IBInspectable
    public var enableDoubleTapForFrameReset: Bool { get set }
  • Position mode for a joystick handle. The default (original) is absolute mode.

    See more

    Declaration

    Swift

    public enum HandlePositionMode
  • How the handle is moved with the initial touch

    Declaration

    Swift

    public var handlePositionMode: HandlePositionMode
  • Minimum distance in either X or Y coordinate the handle must move for handleHasMoved to return true.

    Declaration

    Swift

    public var handleMovedTolerance: CGFloat
  • The image to use to show the handle of the joystick

    Declaration

    Swift

    internal var handleImageView: UIImageView
  • Initialize new joystick view using the given frame.

    Declaration

    Swift

    public override init(frame: CGRect)

    Parameters

    frame

    the location and size of the joystick

  • Initialize new joystick view from a file.

    Declaration

    Swift

    public required init?(coder: NSCoder)

    Parameters

    coder

    the source of the joystick configuration information

Touch Handling

  • A touch began in the joystick view

    Declaration

    Swift

    public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

    Parameters

    touches

    the set of UITouch instances, one for each touch event

    event

    additional event info (ignored)

  • An existing touch has moved.

    Declaration

    Swift

    public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)

    Parameters

    touches

    the set of UITouch instances, one for each touch event

    event

    additional event info (ignored)

  • An existing touch event has been cancelled (probably due to system event such as an alert). Move joystick to center of base.

    Declaration

    Swift

    public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

    Parameters

    touches

    the set of UITouch instances, one for each touch event (ignored)

    event

    additional event info (ignored)

  • User removed touch from display. Move joystick to center of base.

    Declaration

    Swift

    public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)

    Parameters

    touches

    the set of UITouch instances, one for each touch event (ignored)

    event

    additional event info (ignored)

  • Reset our base to the initial location before the user moved it. By default, this will take place whenever the user double-taps on the joystick handle.

    Declaration

    Swift

    @objc
    public func resetFrame()

Implementation Details

  • This is the appropriate place to configure our internal views as we have our own geometry.

    Declaration

    Swift

    public override func layoutSubviews()
  • Returns true if the handle has moved, where moving means the displacement in either coordinate is handleMovedTolerance or greater.

    Declaration

    Swift

    public var handleHasMoved: Bool { get }
  • Implementation of gesture recognizer delegate method. Controls whether a gesture recognizer should continue to track events. This is always the case when in absolute mode (original behavior), but in relative mode it is only allowed until the handle has moved a meaningful amount.

    Declaration

    Swift

    public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool

    Parameters

    gestureRecognizer

    the gesture recognizer being queried

    Return Value

    true if the gesture recognizer can continue to track events

  • Report the current joystick values to any registered monitor.

    Declaration

    Swift

    internal func reportPosition()
  • Install an Obj-C block that will receive the polar coordinates of the joystick.

    Declaration

    Swift

    @objc
    public func setPolarMonitor(_ block: @escaping (CGFloat, CGFloat) -> Void)

    Parameters

    block

    the block to install. It must expect two CGFloat values, first being the angle, and the second being the displacement

  • Install an Obj-C block that will receive the XY unit coordinates of the joystick.

    Declaration

    Swift

    @objc
    public func setXYMonitor(_ block: @escaping (CGFloat, CGFloat) -> Void)

    Parameters

    block

    the block to install. It must expect two CGFloat values, first being the X unit coordinate, and the second being the Y unit coordinate.