swiftswiftui

Why are Swift package extensions available without repeated imports?


For my SwiftUI App I use my own package MyPackage, which is a private Github Repo, for some reusable UI Components and modifiers. It is implemented via SPM and includes handy Views as well as some extensions on View for example:

public extension View {
    
    /// Shimmering loading animation
    /// - Parameter isLoading: True if loading animation should play
    @ViewBuilder func isLoading(_ isLoading: Bool = true) -> some View {
        ...
    }
}

Now to use isLoading() I would first import MyPackage and then apply the modifier on any View in that file. But I noticed that on subsequent uses (in other files) I did not have to import the package.
However when I want to access a View from MyPackage I still need to import it. So that leads me to think that this behavior applies only to extensions on existing types.

I couldn’t find documentation explaining this. What are the exact rules for when an import is needed in Swift, and why do extensions behave differently from new types?


Solution

  • From this discussion on Swift Forums,

    In Swift, there are rules dictating whether the name of a declaration in another module is considered in scope. For example, if you have a program that uses the swift-algorithms package and you want to use the global function chain() then you must write import Algorithms in the file that references that function or the compiler will consider it out of scope.

    The visibility rules for a member declaration, such as a method declared inside of a struct, are different though. When resolving a name to a member declaration, the member is in scope even if the module introducing the member is only transitively imported. A transitively imported module could be imported directly in another source file, or it could be a dependency of some direct dependency of your program. This inconsistency may be best understood as a subtle bug rather than an intentional design decision, and in a lot of Swift code it goes unnoticed. However, the import rules for members become more surprising when you consider the members of extensions, since an extension and its nominal type can be declared in different modules.

    This is reported as an issue, and a fix for this behaviour is implemented, but you need to enable a feature flag, because this is a source breaking change.

    Add the following to the compiler arguments

    -enable-experimental-feature MemberImportVisibility
    

    Now you will not be able to use the View extension unless you import it directly in the file.