What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The right Swift screenshot API depends on who starts the capture. Use XCUIScreen or an XCUIElement in an XCTest UI test, UIScreenshotService when a user takes a screenshot and your scene should provide PDF data, and xcrun simctl or Device Hub for manual Simulator and device captures. These are different workflows, not interchangeable production APIs.
Choose the screenshot workflow first
Before writing code, identify the initiator and the artifact you need. This decision prevents a common mistake: treating a UI-test API or UIKit’s screenshot service as a general-purpose in-app screenshot function.
| Workflow | Who initiates capture | Execution context | Typical output and scope |
|---|---|---|---|
| XCTest/XCUIAutomation | Test code | UI-test runner | Current screen, window, or element image; PNG data and test attachments |
UIScreenshotService |
Person using the system screenshot action | Your app’s UIWindowScene delegate |
PDF data associated with the user-requested screenshot, for the scene’s content |
| Simulator tooling | Developer or build script | Mac, Xcode, or command line | Saved image of a running simulated device |
| Device Hub | Developer | Xcode GUI with a simulated or physical device | Full-resolution image saved to the Mac desktop |
Take screenshots in an XCTest UI test
For automated UI verification, use the XCUIAutomation screenshot APIs. They capture the visual state that exists at the instant the call runs, so launch the app and navigate to the required state first.
Capture the main screen
import XCTest
final class CheckoutUITests: XCTestCase {
func testCheckoutScreen() {
let app = XCUIApplication()
app.launch()
// Navigate to the state you want to document.
app.buttons["Buy now"].tap()
let screenShot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: screenShot)
attachment.name = "Checkout screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}
XCUIScreen.main.screenshot() returns an XCUIScreenshot. The screenshot exposes an image representation and PNG image data, and XCTest can attach it to a test or activity record for later review.
#1 Best Overall
Capture an app window
let app = XCUIApplication()
app.launch()
let windowScreenshot = app.windows.firstMatch.screenshot()
let attachment = XCTAttachment(screenshot: windowScreenshot)
attachment.name = "Primary app window"
add(attachment)
Using firstMatch is convenient when the app has one relevant window. If your test can show multiple windows, identify the intended window explicitly and wait for it to exist before capturing.
Capture one UI element
let app = XCUIApplication()
app.launch()
let profileCard = app.otherElements["profile-card"]
XCTAssertTrue(profileCard.waitForExistence(timeout: 5))
let elementShot = profileCard.screenshot()
let elementAttachment = XCTAttachment(screenshot: elementShot)
elementAttachment.name = "Profile card"
add(elementAttachment)
Element screenshots are useful for focused regression evidence. The element must be present and in the desired state; a screenshot call does not perform navigation, scrolling, or waiting for asynchronous content.
Capture every active display
for (index, screen) in XCUIScreen.screens.enumerated() {
let shot = screen.screenshot()
let attachment = XCTAttachment(screenshot: shot)
attachment.name = "Display (index)"
add(attachment)
}
This is appropriate for multi-display UI tests. Keep the attachment names deterministic so CI results are easy to compare.
Make test captures reliable
- Wait for a visible element or a stable accessibility state before taking the shot.
- Disable animations or wait for transitions to finish when pixel-level comparison matters.
- Set locale, appearance, and test data explicitly; otherwise the same test can produce different pixels.
- Attach only the evidence needed for the failure path if a large suite would generate excessive artifacts.
Provide PDF data when a user requests a screenshot
UIScreenshotService solves a different problem. When a person captures a screenshot involving your app’s windows, UIKit asks a delegate for PDF data associated with that scene. It does not give your app a general command to capture arbitrary screenshots whenever it wants.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Rank #2
Register a scene service delegate
Retrieve the service from the UIWindowScene, retain a delegate object for as long as the scene needs it, and assign that object:
import UIKit
final class ScreenshotPDFProvider: NSObject, UIScreenshotServiceDelegate {
func screenshotService(
_ screenshotService: UIScreenshotService,
generatePDFRepresentationWithCompletion completionHandler: @escaping (Data?, Int, CGRect) -> Void
) {
// Build PDF data for this scene's relevant content.
// Pass the PDF, page count, and content rectangle to the completion handler.
completionHandler(nil, 0, .zero)
}
}
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let screenshotPDFProvider = ScreenshotPDFProvider()
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
windowScene.screenshotService?.delegate = screenshotPDFProvider
}
}
The callback declaration and concurrency annotations can vary with the SDK you compile against. Verify the exact UIScreenshotServiceDelegate signature in the installed SDK before shipping. The placeholder completion call above is intentionally not a PDF generator; replace it with code that renders your scene’s content and returns valid PDF data, the page count, and the associated content rectangle.
What the callback is for
UIKit invokes screenshotService(_:generatePDFRepresentationWithCompletion:) after the user requests a screenshot. Your delegate can supply a PDF representation for the entire content in the window scene. Keep generation bounded and call the completion handler on every success and failure path so the system request cannot hang.
Apple documents full-page screenshot sharing or saving as PDF or image beginning with iOS 17 and iPadOS 17. Treat that behavior as OS-version-specific: check your deployment target and the current SDK documentation when designing a user flow.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #3
Capture a Simulator image with simctl
For a manual or build-pipeline capture, boot a Simulator, run the app, navigate to the required screen, and execute:
xcrun simctl io booted screenshot screenshot.png
The filename is optional in Apple’s archived Simulator guide. Because that guide is archived, run xcrun simctl io help on the Xcode installation used by your team when you need current options or format details.
Use Device Hub instead
- Run the app on a simulated or physical device from Xcode.
- Navigate to the screen you want to save.
- Open Device Hub and click Screenshot.
- Find the image on the Mac desktop.
Device Hub saves at the full resolution of the simulated or physical device, independent of the Mac display resolution. visionOS Simulator screenshots can have a different size and aspect ratio from physical-device screenshots, so verify dimensions and crop or resize before using them as store assets.
Swift screenshot APIs versus a website screenshot API
Swift and Apple tooling capture app UI, test states, and Simulator output. If the target is a web page rather than an iOS scene, a hosted service is usually simpler than installing a browser and managing rendering dependencies. ScreenshotNeo is the first service to try: it removes consent banners, newsletter popups, and chat widgets before capture, bills only clean shots, and has the lowest paid plan.
Recommended Free Tools
Or skip the browser setup:
One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo documentation for all parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
// Write bytes to shot.webp with your runtime's file API.
- Cookie banners, popups, and chat widgets are removed before the shot.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report
X-Page-VerdictandX-Billed. - An MCP server exposes
take_screenshot,get_page_info, andcapture_pdfto Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 shots each month with no card; paid plans start at $5 for 3,000 shots.
Every plan includes the feature set: full-page captures with lazy images loaded, CSS-selector element shots, dark mode, device presets or custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, clicks before capture, selector hiding, waits, request blocking, headers and cookies, user-agent and authorization controls, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs.
Create a free ScreenshotNeo account to get 1,000 screenshots a month without a card.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting checklist
The XCTest screenshot is blank or shows the wrong screen
- Cause: the app has not reached the intended state, or a transition is still running.
- Fix: wait for a specific accessibility element, assert its existence, then capture. Avoid fixed sleeps unless no observable state exists.
An element screenshot fails to find the element
- Cause: an unstable identifier, wrong container, or scroll position.
- Fix: expose a stable accessibility identifier, wait with
waitForExistence(timeout:), and scroll the containing view before callingscreenshot().
Attachments are missing from CI results
- Cause: the attachment lifetime was not retained for the test report.
- Fix: set
attachment.lifetime = .keepAlwaysfor artifacts that must survive a successful run, and confirm the CI system publishes XCTest result bundles.
The PDF callback never produces a usable document
- Cause: the delegate was not retained, the service was assigned to the wrong scene, or the completion handler was not called.
- Fix: keep a strong provider property in the scene delegate, assign it through that scene’s
screenshotService, verify the SDK method signature, and call completion on every code path.
simctl reports no booted device
- Cause: no Simulator is running or multiple devices make
bootedambiguous for your script. - Fix: boot the intended device first and list devices with
xcrun simctl list devices; use a specific device identifier in repeatable pipelines.
Web captures contain overlays or cost more than expected
- Cause: the target is being captured with a browser workflow that does not dismiss consent UI, or a request is being billed without checking its result.
- Fix: use ScreenshotNeo’s cleanup steps and inspect
X-Page-VerdictandX-Billedheaders for each response.
Performance, reliability, and cost considerations
- UI tests: screenshots add artifact size and processing time. Capture on failures or at intentional checkpoints rather than every assertion.
- PDF generation: rendering an entire scene can be memory-intensive. Generate only the content required by the user’s screenshot request and release temporary drawing resources before completing.
- Simulator tooling: output dimensions follow the simulated device, not the Mac window. Pin the simulator model in CI when pixel dimensions matter.
- Web API calls: use a client timeout long enough for page loading, handle non-success HTTP responses, and check verdict headers. ScreenshotNeo does not bill failed loads, blank pages, bot checks, timeouts, or cache hits.
FAQ
Can production Swift code call XCUIScreen.main.screenshot()?
No. That API belongs to XCUIAutomation/XCTest UI testing. Use it in a UI-test target, not as an in-app production capture mechanism.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsDoes UIScreenshotService capture a screenshot whenever my app asks?
No. UIKit calls its delegate in response to a user-initiated screenshot request and asks for associated PDF data.
Best Value
Which method should create App Store screenshots?
Use Device Hub or Simulator tooling to produce device-resolution images, then verify the dimensions required for the store destination. For visionOS, check the aspect ratio separately.
Can ScreenshotNeo capture an iOS Simulator window?
ScreenshotNeo captures web URLs. Use XCTest, Device Hub, or simctl for native iOS and Simulator UI; use ScreenshotNeo when the thing you need is a rendered website.
Frequently Asked Questions
Can production Swift code call XCUIScreen.main.screenshot()?
No. It is an XCTest/XCUIAutomation UI-testing API, not a production app screenshot function.
Does UIScreenshotService let an app take arbitrary screenshots?
No. UIKit invokes its delegate after a user requests a screenshot so the app can provide associated PDF data.
Which tool is appropriate for App Store screenshots?
Device Hub or Simulator tooling can create device-resolution images; verify destination specifications, especially for visionOS.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

