Library / SDK
lyft/Hammer avatar
lyft/Hammer

Hammer: synthesising touches, stylus and keyboard events in iOS unit tests

iOS touch synthesis library

708 stars40 forksSwiftApache-2.0

At a glance

What is it?
Hammer is a Swift library from Lyft that synthesises touch, stylus and keyboard events so a unit test can drive a view the way a user would. It suits teams whose UI logic is worth testing without XCUITest, and it depends on private APIs, so it must never ship inside the app it tests.
Who is it for?
Hammer suits an iOS team that wants to unit test gesture handling in a view or SwiftUI component without paying the cost of a UI test run, and its event generator accepts a window, a view, a view controller or a SwiftUI view, which makes it easy to drop into an existing test target. It is the wrong choice for end-to-end coverage of a real application flow, for a team unwilling to accept private API use in a test binary, or for anything below iOS 17 and Swift 6.0.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Unit tests that tap, rather than UI tests that tap

Hammer is a touch, stylus and keyboard synthesis library for emulating user interaction events, and its stated purpose is enabling better ways of triggering UI actions in unit tests while replicating a real world environment as much as possible.

That positioning is the whole point. XCUITest drives the application from outside, through the accessibility layer, which means a test that exercises a swipe gesture is slow and depends on the app being in a particular state. Hammer stays inside the process. It constructs the same event objects the system would deliver and hands them to the view, so a test can assert on what a tap handler does without launching the app.

The consequences are good and bad. A test written this way runs at unit test speed, runs in the same process as the code under test, and can reach views that are not on screen at all. The bad side is stated bluntly in the project documentation: the library makes extensive use of private APIs and should never be included in a production app. That is a hard constraint, not a suggestion, and it is the first thing to read before considering it.

The topics on the repository name the intended use, ios, lyft, testing and ui-testing. The library is Apache-2.0 licensed, comes from Lyft, and its sources live in a Sources/ directory with Tests/ and a TestHost/ directory beside a Package.swift manifest and a project.yml for XcodeGen.

The host application requirement, and why SwiftPM cannot satisfy it

There is a setup step that catches most people, and it is a real constraint rather than a documentation gap.

Hammer unit tests need to run in a host application in order to generate touches. The project documentation describes the configuration: select your project in the sidebar, select your test target, and choose a host application in the general tab. The host application can be your main application or an empty wrapper, and the repository ships a TestHost directory that serves as such a wrapper.

The consequence for Swift Package Manager users is direct. SwiftPM does not currently support creating applications, so to use Hammer with SwiftPM frameworks you need to create an xcodeproj and set up a host application yourself. In practice that means the library is consumable through SwiftPM, but the test harness around it is an Xcode project, and the repository is set up that way: there is a project.yml at the root for XcodeGen, and a Makefile whose default goal is generate, which runs xcodegen generate.

The Makefile also shows how the project itself is tested. The test target runs lint and then a test-iPad task, which calls xcodebuild with the Hammer project and scheme and a destination of an iPad Pro 13-inch (M5), piping the output through xcbeautify. There are parallel targets for an iPhone 17 and for a simulator running iPhone 15 on iOS 17.5, and the code signing settings are turned off for the test runs, since a test binary on a simulator or a local device does not need signing.

The tools the Makefile installs on a developer machine are swiftlint, xcodegen and xcbeautify, each installed through brew if it is not already present.

An event generator that takes a window, a view or a SwiftUI view

The API centres on one object, the event generator, and it has four ways to be created. Each one handles a different starting point:

swift
// Initialize for an existing UIWindow, ensure that the window is key and visible.
let eventGenerator = try await EventGenerator(window: myWindow)

// Initialize for a UIView, automatically wrapping it in a temporary window.
let eventGenerator = try await EventGenerator(view: myView)

// Initialize for a UIViewController, automatically wrapping it in a temporary window.
let eventGenerator = try await EventGenerator(viewController: myViewController)

// Initialize for a SwiftUI view, automatically hosting it in a temporary window.
let eventGenerator = try await EventGenerator(view: MySwiftUIView())

Only the first case requires anything of the caller, since the window has to be key and visible. The other three wrap the target in a temporary window, which is what makes a unit test on an off-screen view possible at all.

The location of a touch can be given four ways: nothing, which means the centre of the screen; a CGPoint in screen coordinates; a UIView or UIViewController, which uses the centre of its visible part; or an accessibility identifier string of a view or SwiftUI element, which resolves to the centre of the visible part or the activation point of the SwiftUI element. The identifier case is the most useful in a test, since it survives layout changes and it names the thing you mean rather than its coordinates.

One visual detail: by default Hammer displays the simulated touches over the view, and the behaviour is a property on the generator, so a test that asserts on rendering can turn it off with eventGenerator.showTouches = false.

The whole API is main-actor bound and async, which the documentation states directly, with the example test marked @MainActor and using async throws.

Primitive events and the gestures built from them

Hammer separates raw events from the convenience gestures built on top of them, and the primitive calls are the interesting half for anyone writing a test of a custom gesture recogniser.

A finger press is three calls, a down, a move and an up, and the move takes a duration that the library interpolates over:

swift
try await eventGenerator.fingerDown(at: CGPoint(x: 10, y: 10))
try await eventGenerator.fingerMove(to: CGPoint(x: 20, y: 10), duration: 0.5)
try await eventGenerator.fingerUp()

The same three-call shape works for the stylus, which adds the properties an Apple Pencil carries. Stylus input is available when running on an iPad, and the stylusDown call takes azimuth, altitude and pressure alongside the location.

Above that sit the named gestures. fingerTap, fingerDoubleTap, fingerLongPress and twoFingerTap are the common ones, and the documentation notes that they default to the centre of the view when no location is given. The advanced set covers a drag between two points, a pinch between two distances, and a rotation by an angle, each of which also takes a duration:

swift
try await eventGenerator.fingerDrag(from: CGPoint(x: 10, y: 10), to: CGPoint(x: 20, y: 10), duration: 0.5)
try await eventGenerator.fingerPinch(fromDistance: 100, toDistance: 50, duration: 0.5)
try await eventGenerator.fingerRotate(angle: .pi, duration: 0.5)

Multiple fingers can be handled at once, up to the limit the device allows, and a specific finger index can be requested or left to the library to choose.

Keyboard input follows the same shape. Those methods take an explicit KeyboardKey object or a Character, and characters are mapped to their closest keyboard key.

The API also has higher level helpers for locating a subview, waiting for a condition, and finishing the test, and the documentation has separate sections for Swift Testing and for synchronous tests, plus an upgrading guide for people moving from the 1.x line.

What Hammer cannot do, and the version oddity in the install docs

The first limit is the one already stated, private APIs. Hammer reaches into behaviour that Apple does not publish, which means it can break on an OS update, and it means the library can never be linked into a shipping application. Any team with a policy against private API usage in its codebase, even in test-only code, has to rule it out.

The second is scope. Hammer synthesises events for a view that is already in a window. It does not launch an application, does not move between screens, does not talk to the accessibility system the way XCUITest does, and does not verify that something is actually visible on screen. If what you need to test is a flow that crosses screens and involves system dialogs, this is the wrong tool and XCUITest remains the answer.

The third is the version floor. Hammer requires Swift 6.0 and iOS 17.0 or later, which is a real constraint for a project that still supports older operating systems, since the test target would have to raise its minimum.

The fourth is an inconsistency in the install instructions that is worth checking before you add the dependency. The SwiftPM snippet asks for the package at version 2.0.0 or later, while the published releases listed for the repository are 1.0.0 from 2026-10-16, 0.18.0 from 2025-12-03 and 0.17.0 from 2025-02-13. Either a 2.x release exists that is not reflected in the tag list, or the documented version floor is ahead of what has been published. The CocoaPods route is worse: it is explicitly deprecated and will not be updated, with versions up to 0.15.0 remaining on the trunk and newer releases available only through SwiftPM.

All of that is test-only tooling, so none of it affects shipping code, but it does affect whether your dependency resolution succeeds on day one.

Editorial conclusion

Hammer suits an iOS team that wants to unit test gesture handling in a view or SwiftUI component without paying the cost of a UI test run, and its event generator accepts a window, a view, a view controller or a SwiftUI view, which makes it easy to drop into an existing test target. It is the wrong choice for end-to-end coverage of a real application flow, for a team unwilling to accept private API use in a test binary, or for anything below iOS 17 and Swift 6.0. Before adopting it, confirm your test target has a host application, since SwiftPM cannot create one, and check the version you depend on, because the install instructions point at 2.0.0 while the newest published tag is 1.0.0 from 2026-10-16 and the last push was on 2026-10-20.

Frequently asked questions

How do I add Hammer to an iOS project?

With SwiftPM, add .package(url: "https://github.com/lyft/Hammer.git", from: "2.0.0") to your dependencies. CocoaPods support is deprecated and will not be updated, with versions up to 0.15.0 still on the trunk and newer releases only through SwiftPM.

Why does Hammer need a host application for unit tests?

Hammer unit tests need to run in a host application to be able to generate touches. You select a host application in the general tab of your test target, using your main app or an empty wrapper such as the TestHost in the repository, and SwiftPM cannot create applications, so an xcodeproj is needed.

Can Hammer be used in a shipping application?

No. The documentation warns that the library makes extensive use of private APIs and should never be included in a production app.

What do Hammer tests need in terms of Swift and iOS versions?

Swift 6.0 and iOS 17.0 or later. The library also runs on the main actor and its API is async, so tests using it should be async and isolated to the main actor.

What licence is Hammer released under?

Apache-2.0, with the LICENSE and NOTICE files at the root of the repository.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. lyft/Hammer on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lyft-hammer.svg)](https://hysenlabs.com/projects/lyft-hammer)

Was this useful?