{"id":20010607,"url":"https://github.com/bradhowes/joystick","last_synced_at":"2025-10-31T17:30:27.254Z","repository":{"id":48107521,"uuid":"75472611","full_name":"bradhowes/Joystick","owner":"bradhowes","description":"A custom UIView in Swift that presents a simple joystick interface.","archived":false,"fork":false,"pushed_at":"2024-08-17T23:33:07.000Z","size":7961,"stargazers_count":59,"open_issues_count":0,"forks_count":19,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-10-21T03:45:30.736Z","etag":null,"topics":["arcade-controller","cocoapods","ios","joystick","swift","swift-package","swift-playgrounds","uiimageview"],"latest_commit_sha":null,"homepage":"https://bradhowes.github.io/Joystick/index.html","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bradhowes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"bradhowes"}},"created_at":"2016-12-03T12:34:37.000Z","updated_at":"2024-08-17T23:33:11.000Z","dependencies_parsed_at":"2024-01-10T21:59:27.973Z","dependency_job_id":"b8ffc4b3-5447-495c-8bad-d8f66de2eae5","html_url":"https://github.com/bradhowes/Joystick","commit_stats":{"total_commits":185,"total_committers":3,"mean_commits":"61.666666666666664","dds":"0.021621621621621623","last_synced_commit":"4abc2045e64d26c452d492311740cc3f572be044"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2FJoystick","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2FJoystick/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2FJoystick/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2FJoystick/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bradhowes","download_url":"https://codeload.github.com/bradhowes/Joystick/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239217105,"owners_count":19601594,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arcade-controller","cocoapods","ios","joystick","swift","swift-package","swift-playgrounds","uiimageview"],"created_at":"2024-11-13T07:20:45.449Z","updated_at":"2025-10-31T17:30:27.197Z","avatar_url":"https://github.com/bradhowes.png","language":"Swift","funding_links":["https://github.com/sponsors/bradhowes"],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/bradhowes/Joystick/workflows/CI/badge.svg)](https://github.com/bradhowes/Joystick)\n[![COV](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/bradhowes/4739895cbf46573cc4b63ab776c74899/raw/Joystick-coverage.json)](https://github.com/bradhowes/Joystick/blob/main/.github/workflows/CI.yml)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbradhowes%2FJoystick%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/bradhowes/Joystick)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbradhowes%2FJoystick%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/bradhowes/Joystick)\n[![License: MIT](https://img.shields.io/badge/License-MIT-A31F34.svg)](https://opensource.org/licenses/MIT)\n\n# Joystick\n\n![](https://github.com/bradhowes/Joystick/blob/main/animation.gif?raw=true)\n\nA custom UIView in Swift that presents a simple joystick interface. The custom view consists of two UIImageView\ninstances, one for the base and one for the handle. When the user moves the handle, it will report out a value\nbased on its position in relation to the joystick base. The type of information reported depends on the type of\n[monitor](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L31) installed:\n\n* [JoyStickViewMonitorKind.polar](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickViewMonitor.swift#L80) -- reports out instances of [JoyStickViewPolarReport](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickViewMonitor.swift#L36) with\n  * angle -- the direction the handle is point, given in degrees, where north/up is 0° and east/right is 90°\n  * displacement -- the distance from the center the handle moved, from 0.0 to 1.0 with 1.0.\n* [JoyStickViewMonitorKind.xy](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickViewMonitor.swift#L87) -- reports out instances of [JoyStickViewXYReport](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickViewMonitor.swift#L9) with\n  * x -- horizontal offset from the center of the base, where east/right is positive\n  * y -- vertical offset from the center of the base, where north/up is positive\n\n```swift\nlet monitor: JoyStickViewPolarMonitor = {\n    print(\"\\(String(format: \"%.2f°\", $0.angle)) \\(String(format: \"%.3f\", $0.displacement))\")\n}\n\njoystick.monitor = .polar(monitor: monitor2)\n```\n\nThere is also support (since 3.0.1) for using an Objective-C block as a monitor, with a slight reduction in type\nsafety. The `setPolarMonitor` and `setXYMonitor` both take a closure that accepts two `CGFloat` arguments and\nreturns no value. Objective-C blocks can be used as well as Swift closures in these methods. Since 3.1.0, there is also\na `tappedBlock` attribute which one can use to receive a notification when the user just taps on the joystick handle.\n\nThe view supports an option ([movable](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L62))\nwhere the view will move when the user moves the handle to a\ndisplacement beyond 1.0. This can be useful when the initial position of the joystick in an app is not ideal for\nthe user's thumb. Double-tapping on the joystick moves it back to its original position.\n\nIn the animation above, there are two joysticks, one green and one magenta. The green is *fixed* and does not\nmove even when the touch motion would cause a displacement larger than 1.0. The magenta joystick however is\n*movable*, with the base following the touch motion. For movable joysticks, the Base motion is optionally\nrestricted to a `CGRect` in the [movableBounds](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L69) property,\nas is the case in the demonstration animation above where the magenta joystick cannot move out of the pink band.\n\n## Additional Properties\n\nHere are some additional configurable features of the JoyStickView:\n\n* [handleConstraint](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L39) -- optional `CGRect` which constrains where the handle can move. See the playground for an example.\n* [baseImage](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L116) -- a UIImage to use for the base of the joystick.\n* [handleImage](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L119) -- a UIImage to use for the handle of the joystick.\n* [baseAlpha](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L83) -- opacity of the base of the joystick.\n* [handleAlpha](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L90) -- opacity of the handle of the joystick.\n* [handleTintColor](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L96) -- optional tint color applied to the joystick image.\n* [handleSizeRatio](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L100) -- scaling applied to the joystick handle's image. Note that default is `0.85` due to\n  historical reasons.\n* [enableDoubleTapForFrameReset](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L123) -- if `movable` is true, allow user to double-tap on view to move base to original\n  location.\n* [handlePositionMode](https://github.com/bradhowes/Joystick/blob/main/Sources/JoyStickView/JoyStickView.swift#L146) -- when set to `.absolute` (default) the handle will move to the initial (constrained) press location. When set to `.relative` the handle will move only after the touch moves.\n\n# Releases\n\n* v3.2.0 -- Offer code as Swift package in addition to CocoaPod packaging\n* v3.1.1 -- Added `handlePositionMode` property to control how handle movements are reported. Default behavior\n  is `.absolute`. New `.relative` mode offers finer control at initial touch (thanks to [Michael Tyson](https://github.com/michaeltyson))\n* v3.1.0 -- Added `tappedBlock` property (thanks to [Michael Tyson](https://github.com/michaeltyson))\n* v3.0.2 -- Fixed too much scaling in `scaleHandleImageView`\n* v3.0.1 -- Added support for Obj-C monitor blocks\n* v3.0.0 -- Swift 5 (no code changes, only Xcode configury)\n* v2.1.2 -- Swift 4.2\n\n# Code\n\nThe Xcode workspace contains three components:\n\n- a framework called [JoyStickView](https://github.com/bradhowes/Joystick/tree/master/JoyStickView) (when using CocoaPods `import BRHJoyStickView` instead of `import JoyStickView`)\n- a simple iOS application called [JoyStickViewApp](https://github.com/bradhowes/Joystick/tree/master/JoyStickViewApp)\n- a playground called [JoyStickView Playground](https://github.com/bradhowes/Joystick/tree/master/JoyStickView%20Playground.playground/Contents.swift)\n\nBoth the playground and the app rely on the framework for the JoyStickView UIView.\n\n\u003e :warning: **NOTE**: due to the plumbing for CocoaPods, when editing in Xcode open the workspace **JoyStickView.xcworkspace** instead of the project **JoyStickView.xcodeproj**.\n\nThe Xcode playground code sets up the display environemnt and installs two joysticks, one that is fixed (green)\nand the other that is movable (yellow). Both joysticks report out their positions in two labels, one for angles and\nthe other for displacement.\n\nThe [JoyStickView.swift](https://github.com/bradhowes/Joystick/tree/master/Sources/JoyStickView/JoyStickView.swift) file defines the joystick view and behavior.\nIt resides inside the JoyStickView Swift package, and in the BRHJoyStickView framework in CocoaPods. There you will also find a file called\n[CoreGraphics+Additions.swift](https://github.com/bradhowes/Joystick/tree/master/Sources/JoyStickView/CoreGraphics+Additions.swift) that contains various\nextensions to some CoreGraphics structs that allow for some simplified mathematical expressions in the [JoyStickView](https://github.com/bradhowes/Joystick) code.\n\nBy default the [JoyStickView](https://github.com/bradhowes/Joystick/tree/master/Sources/JoyStickView/JoyStickView.swift) class uses two image assets found in the\n[Assets](https://github.com/bradhowes/Joystick/tree/master/Sources/JoyStickView/Resources/Assets.xcassets) container.\nfolder:\n\n* DefaultBase — the image to use for the base of the joystick\n* DefaultHandle — the image to use for the handle of the joystick. **Note**: this will be tinted with the `handleTintColor` setting\n\nBoth exist in three resolutions for the various iOS devices out today. They were generated using the great [Opacity](http://likethought.com/opacity/) app. The\nOpacity documents are included in this repository in the [Resources](https://github.com/bradhowes/Joystick/tree/master/JoyStickViewApp/Resources) directory for\nthe `JoyStickViewApp` demonstration app.\n\nTo use your own images, simple set `baseImage` and/or `handleImage` attributes with the `UIImage` you wish to use.\n\n# Documentation\n\nPlease see the [code documentation](https://bradhowes.github.io/Joystick/) for additional information.\n\n# CocoaPods\n\nThere is a simple [CocoaPods](https://cocoapods.org) spec file available so you can add the code and resources\nby adding \"BRHJoyStickView\" to your `Podfile` file and import with `import BRHJoyStickView`. Currently everything more or less works, except for the fact\nthat pointing to image resources via Interface Builder (IB) will result in invalid UImage results because the files won't be\nfound where IB was able to find them. The only solution is to manually locate those files and set them in your\nview loading code. Something like the following should help:\n\n```swift\nextension Bundle {\n\n    /**\n     Locate an inner Bundle generated from CocoaPod packaging.\n\n     - parameter name: the name of the inner resource bundle. This should match the \"s.resource_bundle\" key or\n       one of the \"s.resoruce_bundles\" keys from the podspec file that defines the CocoPod.\n     - returns: the resource Bundle or `self` if resource bundle was not found\n    */\n    func podResource(name: String) -\u003e Bundle {\n        guard let bundleUrl = self.url(forResource: name, withExtension: \"bundle\") else { return self }\n        return Bundle(url: bundleUrl) ?? self\n    }\n}\n```\n\nIn your setup code, you then will need to do something like so:\n\n```swift\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        let bundle = Bundle(for: JoyStickView.self).podResource(name: \"BRHJoyStickView\")\n        joystick.baseImage = UIImage(named: \"FancyBase\", in: bundle, compatibleWith: nil)\n        joystick.handleImage = UIImage(named: \"FancyHandle\", in: bundle, compatibleWith: nil)\n    }\n```\n\nThe `podResource` method attempts to locate a named inner bundle, defaulting to the original bundle if not found. The\n`viewDidLoad` code will then use the right `bundle` object in the UIImage constructors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbradhowes%2Fjoystick","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbradhowes%2Fjoystick","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbradhowes%2Fjoystick/lists"}