What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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

  1. Run the app on a simulated or physical device from Xcode.
  2. Navigate to the screen you want to save.
  3. Open Device Hub and click Screenshot.
  4. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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-Verdict and X-Billed.
  • An MCP server exposes take_screenshot, get_page_info, and capture_pdf to 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.Support on Ko-Fi

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 calling screenshot().

Attachments are missing from CI results

  • Cause: the attachment lifetime was not retained for the test report.
  • Fix: set attachment.lifetime = .keepAlways for 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 booted ambiguous 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-Verdict and X-Billed headers 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.