How to Refactor Swift in Xcode

How do I refactor Swift in Xcode?

AFAIK the refactoring is not working with Swift right now, however, at least in the same file(scope), you can do the following:
Sample Image

Xcode Refactor Convert to Switch


Update

As you've pointed out, Convert to Switch != Convert To Switch Statement. In order to understand why, we need to know how Xcode refactoring feature works under the hood.

What I've found:

Here's a great article on the topic:
https://levelup.gitconnected.com/uncovering-xcode-indexing-8b3f3ff82551

It points us to a swift open-source repo: https://github.com/apple/swift

Xcode refactoring is done with a SourceKit framework, which is inside a swift repo.

Here's a SourceKit protocol for communication with Xcode: https://github.com/apple/swift/blob/main/tools/SourceKit/docs/Protocol.md

Internally, when we choose "Refactor", Xcode somehow (via XPC) sends something like source.request.workspace.refactoring message to a SourceKit. And there's an "indexed file" somewhere, so SourceKit can instantly (not in 5 minutes :)) propose you the options which are adequate/possible for a current snippet of code.

I've loaded swift repo from github (it's about 30MB), navigated into SourceKit code and tried to find stuff related to source.request.workspace.refactoring.

Somewhere in the code is a reference to swift/IDE/RefactoringKinds.def. I'll copy-paste the contents of this file:

#ifndef REFACTORING
#define REFACTORING(KIND, NAME, ID)
#endif

#ifndef SEMANTIC_REFACTORING
#define SEMANTIC_REFACTORING(KIND, NAME, ID) REFACTORING(KIND, NAME, ID)
#endif

#ifndef RANGE_REFACTORING
#define RANGE_REFACTORING(KIND, NAME, ID) SEMANTIC_REFACTORING(KIND, NAME, ID)
#endif

#ifndef INTERNAL_RANGE_REFACTORING
#define INTERNAL_RANGE_REFACTORING(KIND, NAME, ID) RANGE_REFACTORING(KIND, NAME, ID)
#endif

#ifndef CURSOR_REFACTORING
#define CURSOR_REFACTORING(KIND, NAME, ID) SEMANTIC_REFACTORING(KIND, NAME, ID)
#endif

/// Rename and categorise the symbol occurrences at provided locations
/// (syntactically).
REFACTORING(GlobalRename, "Global Rename", rename.global)

/// Categorize source ranges for symbol occurrences at provided locations
/// (syntactically).
REFACTORING(FindGlobalRenameRanges, "Find Global Rename Ranges", rename.global.find-ranges)

/// Find and categorize all occurences of the file-local symbol at a given
/// location.
REFACTORING(FindLocalRenameRanges, "Find Local Rename Ranges", rename.local.find-ranges)

/// Find and rename all occurences of the file-local symbol at a given
/// location.
CURSOR_REFACTORING(LocalRename, "Local Rename", rename.local)

CURSOR_REFACTORING(FillProtocolStub, "Add Missing Protocol Requirements", fillstub)

CURSOR_REFACTORING(ExpandDefault, "Expand Default", expand.default)

CURSOR_REFACTORING(ExpandSwitchCases, "Expand Switch Cases", expand.switch.cases)

CURSOR_REFACTORING(LocalizeString, "Localize String", localize.string)

CURSOR_REFACTORING(SimplifyNumberLiteral, "Simplify Long Number Literal", simplify.long.number.literal)

CURSOR_REFACTORING(CollapseNestedIfStmt, "Collapse Nested If Statements", collapse.nested.ifstmt)

CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch)

CURSOR_REFACTORING(TrailingClosure, "Convert To Trailing Closure", trailingclosure)

CURSOR_REFACTORING(MemberwiseInitLocalRefactoring, "Generate Memberwise Initializer", memberwise.init.local.refactoring)

CURSOR_REFACTORING(AddEquatableConformance, "Add Equatable Conformance", add.equatable.conformance)

CURSOR_REFACTORING(ConvertCallToAsyncAlternative, "Convert Call to Async Alternative", convert.call-to-async)

CURSOR_REFACTORING(ConvertToAsync, "Convert Function to Async", convert.func-to-async)

CURSOR_REFACTORING(AddAsyncAlternative, "Add Async Alternative", add.async-alternative)

RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)

RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)

RANGE_REFACTORING(ExtractRepeatedExpr, "Extract Repeated Expression", extract.expr.repeated)

RANGE_REFACTORING(MoveMembersToExtension, "Move To Extension", move.members.to.extension)

RANGE_REFACTORING(ConvertStringsConcatenationToInterpolation, "Convert to String Interpolation", convert.string-concatenation.interpolation)

RANGE_REFACTORING(ExpandTernaryExpr, "Expand Ternary Expression", expand.ternary.expr)

RANGE_REFACTORING(ConvertToTernaryExpr, "Convert To Ternary Expression", convert.ternary.expr)

RANGE_REFACTORING(ConvertIfLetExprToGuardExpr, "Convert To Guard Expression", convert.iflet.to.guard.expr)

RANGE_REFACTORING(ConvertGuardExprToIfLetExpr, "Convert To IfLet Expression", convert.to.iflet.expr)

RANGE_REFACTORING(ConvertToComputedProperty, "Convert To Computed Property", convert.to.computed.property)

RANGE_REFACTORING(ConvertToSwitchStmt, "Convert To Switch Statement", convert.switch.stmt)

// These internal refactorings are designed to be helpful for working on
// the compiler/standard library, etc., but are likely to be just confusing and
// noise for general development.

INTERNAL_RANGE_REFACTORING(ReplaceBodiesWithFatalError, "Replace Function Bodies With 'fatalError()'", replace.bodies.with.fatalError)

#undef CURSOR_REFACTORING
#undef INTERNAL_RANGE_REFACTORING
#undef RANGE_REFACTORING
#undef SEMANTIC_REFACTORING
#undef REFACTORING

As you see, there's Convert To Switch Statement here, and Expand Switch Cases, but not Convert To Switch.

So my guess is that what Xcode shows us in UI, is either a bug (they forgot to delete an outdated stuff), or a part of some implementation which is not in a current version of a Swift repo.

Anyway, you may dig even deeper - I've just tried to investigate it in a short period of time. :)
If you really need this done, then except of exploring SourceKit protocols, I guess you can open an issue on Github - maybe those who know it will explain a real reason.

Initial answer

Let's consider such an example:

enum SomeEnum {
case option1, option2, option3
}

func convertToSwitchExampleFunc(_ option: SomeEnum) {
if option == .option1 {
print("1")
}
if option == .option2 {
print("2")
}
if option == .option3 {
print("3")
}
}

func callingFunc() {
convertToSwitchExampleFunc(.option2)
}

If you select all code inside the body of convertToSwitchExampleFunc, then choose Refactor, Xcode won't show you "Convert to Switch".

But if you change the code such that it's more similar to an ugly "switch" implementation (if-else-if-else chain):

func convertToSwitchExampleFunc(_ option: SomeEnum) {
if option == .option1 {
print("1")
}
else if option == .option2 {
print("2")
}
else if option == .option3 {
print("3")
}
}

Then Xcode will show you "Convert to Switch menu", and an auto-refactored code will look like this:

func convertToSwitchExampleFunc(_ option: SomeEnum) {
switch option {
case .option1:
print("1")
case .option2:
print("2")
case .option3:
print("3")
default:
break
}
}

Just to give more context:
Sample Image

Xcode 9.1 Refactoring not available

Try cleaning your Xcode. Also try clearing out derived data and restart Xcode.

Clean Build --> (Command-Option-Shift-K)
Delete DerivedData folder in ~/Library/Developer/Xcode/DerivedData

Code refactoring - Swift

You can use a single IBAction instead of 4 separate methods. And make the sender type to UIButton from Any. Then you can do something like

@IBAction func toggleEnableDisable(_ sender: UIButton) {
let currentValue = sender.isEnabled
let buttonArray = [trainingButton, friendlyMatch, tournamentButton, competitiveMatch]
buttonArray.forEach { $0.isEnabled = false }
sender.isEnabled = !currentValue
}

Swift refactoring: How quickly change all access modifiers to public

You can make it in Find navigator. Choose Replace -> Text -> Matching Word and write something like this:

Sample Image

Choose your framework by clicking on "In Workspace". Than click "Replace all".

Do this with classes and structures. But be attentive with func and var/let because it can be inside some function.

Rename or refactor files in Xcode

The safest way to rename files is to refactor the class name using Xcode's "Refactor" command. Using that command makes sure that your code is also updated anywhere that references the file or the class (including NIBs).

Right-click on the class name in the interface (.h) file, choose Refactor->Rename, and Xcode will guide you through the process, allowing you to preview changes before you commit to them.

More details:

Right click on the class name (after @interface) and choose Refactor->Rename. For example, right click on ViewController in this code:

@interface ViewController : UIViewController

Sample Image

Enter the new name for the class:

Sample Image

Xcode will let you review the changes before you commit.

Sample Image

You can also highlight the class name and use Xcode's Edit->Refactor menu, or even map it to a keyboard shortcut if you want.

If you actually want to rename the file without affecting any code, click on the name in the Navigator pane (left pane), wait a second, then click again. The name will be highlighted and you can type in a new one. You can also delete from the Navigator, rename the file in the Finder and re-add it to your project.



Related Topics



Leave a reply



Submit