I recently found myself rediscovering Xcode’s multi-cursor support. Here’s a reminder in case you need it.
Last updated: Mar 27, 2023
Column Editing
I’ve never known what to call it, but I think the multi-cursor feature that I use the most starts by holding down the option (⌥) key and dragging to select a column. Here I’ve dragged down with the mouse to select the let
keyword on four consecutive lines:
If you don’t want to use the mouse you can select a column above or below the cursor position using the ⌃⇧+Up or ⌃+⇧+Down keys. Now if I type var
I replace the let
on all four lines in one go:
Note the four cursors positioned at the end of the let
. You can move all four cursors with keyboard arrow keys or hit the escape key to get out of multi-cursor mode.
Multi-cursor Editing (Control-Shift-Click)
Multi-cursor editing is not new. Apple added support to Xcode 10 back in 2018. That was apparently a big year for the source editor as Apple published separate release notes:
Here’s what they said about multi-cursor editing:
The Xcode Source Editor now supports multi-cursor editing allowing you to quickly edit multiple ranges of code at once. You can place additional cursors with the mouse via ⌃+⇧+Click or with column select (⌥+Click+Drag), or with the keyboard using ⌃+⇧+Up to column select up or ⌃+⇧+Down to column select down. (12564506)
We’ve already seen the column selection. What I forget exists is that you can ⌃+⇧+Click anywhere to add extra cursors. For example, here’s a SwiftUI view that I moved to a Swift Package where I want to make the methods and properties public:
struct ErrorView: View {
var error: Error?
init(error: Error? = nil) { ... }
var body: some View { ... }
}
Starting with the cursor positioned at the start of the first line (before the s
in struct
). Using ⌃+⇧+Click I can add extra cursors before the two var
properties and the init
method:
Typing public
then changes all four places in the source code:
Use ⌃+⇧+Click on a cursor to remove it.
With Copy and Paste
A tip I saw used in the WWDC 2018 State of the Union video is using multiple cursors with copy and paste. For example, here’s an enum I used when experimenting with adaptive stack views:
public enum Condition {
case compact
case regular
case accessible
case compactAccessible
case regularAccessible
}
Each of the condition cases has a method with the same name that I want to return from a switch. Xcode code completion fills out the switch for me:
I need to replace each of the code placeholders with a return statement. Back at the enum I can ⌃+⇧+Click to place a cursor at the start of each of the condition names:
Holding the ⇧ key, a ⌘+Right-arrow selects to the end of the line which I can then ⌘+C copy to the clipboard.
Back at the switch statement, I can add cursors at each of the code placeholders and again select to the end of the line with ⇧+⌘+Right-arrow:
Now I can type return
, add a space, then ⌘+V to paste in the condition names. Xcode is smart enough to paste the six lines from the clipboard at each of the six cursor points:
To clean things up I can even Up-arrow to move all the cursors up a line and delete the new-line and spacing:
static private func handler(_ condition: Condition) -> ConditionHandler {
switch condition {
case .compact: return compact
case .regular: return regular
case .accessible: return accessible
case .compactAccessible: return compactAccessible
case .regularAccessible: return regularAccessible
case .compactOrAccessible: return compactOrAccessible
}
}
As with all keyboard shortcuts it takes a few repetitions for me to learn it.
Select Next Occurrence (⌥⌘E)
A number of people let me know about another way to add multiple cursors using ⌥⌘E (in the Find menu as Select Next Occurrence). There’s also a Select Previous Occurrence (⌥⇧⌘E) but I doubt I’ll ever remember that.
Consider this example of two methods that check for compact size conditions. I want to change all four occurrences of compact
to regular
:
static private func compact(horizontalSizeClass: UserInterfaceSizeClass?,
dynamicTypeSize: DynamicTypeSize) -> Bool {
horizontalSizeClass == .compact
}
static private func compactAccessible(horizontalSizeClass: UserInterfaceSizeClass?,
dynamicTypeSize: DynamicTypeSize) -> Bool {
horizontalSizeClass == .compact &&
dynamicTypeSize.isAccessibilitySize
}
Start by selecting the first compact, then use ⌥⌘E to select the next three occurrences:
Now type “regular” and all four occurrences are replaced and you’re left with four cursors in position if you need to make further changes:
Learn More
There’s a quick demo of multi-cursor editing in the WWDC 2018 State of the Union: