Why this happens
A playground inside an Xcode project is not part of your app target. It is a separate file that Xcode compiles and runs on its own, and it can only call into your app’s code if the app module has been built and linked into the playground.
That is why public does not help. public controls visibility across module boundaries, but the playground still has to have your app module available in the first place. When the module is missing or stale, the compiler reports Cannot find 'functionName' in scope even though the function exists, is public, and sits in a file right next to the playground in the Project Navigator.
The same root cause explains the refactoring annoyance: rename a function in a normal .swift file and the playground copy of the call site is not updated, because the rename refactoring only walks the files in the compilation context it was started from. Playgrounds are not in that context.
The fix is almost always in the playground’s settings and the active destination — not in your function’s access level.
How to fix it
1. Enable Import App Types and Build Active Scheme
Select the playground file in the Project Navigator, then open the File Inspector in the right sidebar (Option-Command-1). When a playground is selected you get a Playground Settings section with two checkboxes:
- Import App Types — makes your app target’s types and functions visible to the playground.
- Build Active Scheme — tells Xcode to build the active scheme so those symbols are actually available.
With both checked, Xcode builds the project behind the scenes and the playground picks up your app’s symbols. With them off, the playground can only see the standard library and whatever you put in its own Sources folder.
After enabling them, build once with Command-B and let the playground re-run before you assume it is still broken:
// In your app target: FeatureFlags.swift
public struct FeatureFlags {
public static let newCheckout = true
public static func isEnabled(_ name: String) -> Bool {
name == "newCheckout" && newCheckout
}
}
// In the playground
let enabled = FeatureFlags.isEnabled("newCheckout") // now resolves
print(enabled)
2. Check the file’s target membership
A file can be in the project folder and still not be compiled into the app target. Select the .swift file that declares your function, open the File Inspector, and look at Target Membership. Your app target must be checked.
If it is not, check it, build, and the playground can resolve the symbol. Files that belong only to a test target, an extension, or no target at all never reach the app module, so no playground setting will make them visible.
3. Use a Simulator as the run destination
In-project playgrounds need a concrete build destination. Pick a simulator from the scheme/destination menu — not a physical device and not Any iOS Device. Playground execution against a generic or device destination leaves the app module built for a platform the playground is not running on, and symbol lookup fails.
After switching the destination, build with Command-B once so the module exists for that destination.
4. Move playground-only helpers into the playground’s Sources folder
If the function is a scratch helper you only ever call from the playground, drag its file into the playground’s own Sources folder in the Navigator. Files there are compiled as part of the playground’s private module — no app build, no destination, no target membership involved.
// Playground Sources/Helpers.swift
public func normalized(_ input: String) -> String {
input.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
The catch is that code in Sources cannot reference your app’s types, so this works for self-contained utilities only.
5. Clear stale state after a rename
If the function existed before and stopped resolving after you renamed or moved it, the playground is holding a stale index. In order:
- Build (Command-B).
- Close the playground and reopen it from the Project Navigator.
- Product > Clean Build Folder (Shift-Command-K), then build again.
- If it still fails, quit Xcode and delete the project’s DerivedData folder, then reopen.
Also fix the call site by hand. Rename refactoring does not reach into playgrounds, so the old name stays in the playground after the function is renamed — which is exactly the Cannot find ... in scope error again.
A better arrangement: a local Swift package
If your playground keeps choking on app code, extract the reusable types and functions into a local Swift package and add it to both the app target and the playground. Packages are real modules with a proper build graph, so the playground links them directly and re-indexes reliably. It also removes the whole “is the app built for this destination” question from your workflow.
How to prevent it
- Keep Import App Types and Build Active Scheme enabled on every in-project playground you create.
- Stay on a Simulator destination while doing playground work, and check the destination first when a symbol suddenly disappears.
- Move finished functions out of the playground into the app target, then delete the playground copy — do not keep two versions of the same function.
- After any rename or move, build once with Command-B before blaming the compiler.
- Keep the playground small. Prototype one function, move it, then delete the scratch code.
FAQ
Does making the function public let a playground see it?
No. public only widens visibility across module boundaries. The playground must first have your app module built and linked, which is what Import App Types and Build Active Scheme do.
Do I need to write import MyApp at the top of the playground?
Usually not. With Import App Types enabled, Xcode wires the app module into the playground automatically. If you added a local package instead, do import that package’s module explicitly.
Why does my in-project playground get slow with lots of code?
Because every run rebuilds and links the app module alongside the playground. Large amounts of scratch code make that cycle worse. Keep the playground lean and push stable code into a package or the app target.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
