【SwiftUI】@Environmentの使い方を徹底解説

この記事では、@Environmentの使い方について徹底解説していきたいと思います。

@Environmentとは

@Environmentとは、簡単いうと、その端末の状態や、Viewの状態を読み取れるカスタム属性です。

例えば、その端末がダークモードなのかライトモードなのか、その端末のピクセル数はどのくらいなのか、その端末の文字の大きさの設定はどのくらいなのか、その端末はモーダルを開いている状態なのかどうかなど、その端末やViewの状態が取得できます。

この@Environmentは自作することもできます。

使い方

では、使い方について解説していきたいと思います。@Environmentは色々な使い方があるので、よく使うものや重要なものを紹介していきたいと思います。

ダークモードかライトモードか判定

以下のように、@Environmentで\.colorSchemeと記載することで、ダークモードかライトモードかの判定が取得できます。

@Environment(\.colorScheme) var colorScheme: ColorScheme

struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme: ColorScheme
    
    var body: some View {
        if colorScheme == .dark {
            Text("dark mode")
        } else if colorScheme == .light {
            Text("light mode")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
//            .preferredColorScheme(.dark)
    }
}

ちなみに、プレビュー部分の.preferredColorScheme(.dark)をコメントアウト解除すると端末がダークモードになります。

モーダルを閉じる

これはよく使うと思います。

struct ContentView: View {
    @State var isShowNextView = false
    
    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
            Button("NextViewへ") {
                isShowNextView = true
            }
            .sheet(isPresented: $isShowNextView) {
                NextView()
            }
        }
    }
}

struct NextView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        VStack {
            Text("BView")
                .padding()
            Button("閉じる") {
                dismiss()
            }
        }
    }
}
ちなみに

iOS14以前では、@Environment(\.dismiss) var dismissという@Environmentはなく以下のような書き方をしていました。こちらの書き方は、iOS15以降、非推奨になっていますので、上記の書き方で書くようにしてください。

struct NextView: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        VStack {
            Text("BView")
                .padding()
            Button("閉じる") {
                presentationMode.wrappedValue.dismiss()
            }
        }
    }
}

モーダルを開いているかどうかの判定

struct ContentView: View {
    @Environment(\.isPresented) var isPresented
    @Environment(\.dismiss) var dismiss
    
    @State var isShowModalView = false
    
    var body: some View {
        if !isPresented {
            VStack {
                Button("モーダル遷移") {
                    isShowModalView = true
                }
                .sheet(isPresented: $isShowModalView) {
                    ContentView()
                }
            }
        } else {
            VStack {
                Button("閉じる") {
                    dismiss()
                }
            }
        }
    }
}
ちなみに

こちらもdismissと同じように、iOS14以前では、@Environment(\.presentationMode) var presentationModeという@Environmentで、
presentationMode.wrappedValue.isPresentedという書き方をしていました。この書き方は、iOS15以降、非推奨になっているので上記の書き方で書いてください。

@Environmentで使える値一覧

上記で紹介した@Environment以外にもたくさんあります。紹介しきれないほどあるので、詳しくはAppleの公式サイトをご確認ください。

  • accessibilityDifferentiateWithoutColor: Bool
  • accessibilityEnabled: Bool
  • accessibilityInvertColors: Bool
  • accessibilityReduceMotion: Bool
  • accessibilityReduceTransparency: Bool
  • accessibilityShowButtonShapes: Bool
  • legibilityWeight: LegibilityWeight?
  • colorScheme:ColorScheme
  • colorSchemeContrast:ColorSchemeContrast
  • controlSize:ControlSize
  • controlProminence: Prominence
  • controlActiveState: ControlActiveState
  • defaultWheelPickerItemHeight: CGFloat
  • headerProminence: Prominence
  • isEnabled: Bool
  • menuIndicatorVisibility: Visibility
  • managedObjectContext: NSManagedObjectContext
  • calendar: Calendar
  • locale: Locale
  • timeZone: TimeZone
  • displayScale: CGFloat
  • imageScale: Image.Scale
  • pixelLength: CGFloat
  • isLuminanceReduced: Bool
  • editMode: Binding?
  • isFocused: Bool
  • resetFocus: ResetFocusAction
  • keyboardShortcut: KeyboardShortcut?
  • defaultMinListHeaderHeight: CGFloat?
  • defaultMinListRowHeight: CGFloat
  • backgroundMaterial: Material?
  • isPresented: Bool
  • dismiss: DismissAction
  • redactionReasons: RedactionReasons
  • refresh: RefreshAction?
  • scenePhase: ScenePhase
  • isSearching: Bool
  • dismissSearch: DismissSearchAction
  • horizontalSizeClass: UserInterfaceSizeClass?
  • verticalSizeClass: UserInterfaceSizeClass?
  • symbolRenderingMode: SymbolRenderingMode?
  • symbolVariants: SymbolVariants
  • allowsTightening: Bool
  • disableAutocorrection: Bool?
  • dynamicTypeSize: DynamicTypeSize
  • font: Font?
  • layoutDirection: LayoutDirection
  • lineLimit: Int?
  • lineSpacing: CGFloat
  • minimumScaleFactor: CGFloat
  • multilineTextAlignment: TextAlignment
  • textCase: Text.Case?
  • truncationMode: Text.TruncationMode
  • undoManager: UndoManager?
  • openURL: OpenURLAction
  • widgetFamily: WidgetFamily
  • description: String

@Environmentを自作する方法

@Environmentは自作することができます。

あまり自作をしてまで使う機会はないと思いますが、意外と簡単なので紹介しておきます。

struct ContentView: View {
    @Environment(\.mainColor) var mainColor
    
    var body: some View {
        Text("Hello, world!")
            .padding()
            .background(mainColor)
    }
}

struct MainColorKey: EnvironmentKey {
    static let defaultValue: Color = .orange
}

extension EnvironmentValues {
    var mainColor: Color {
        get { self[MainColorKey.self] }
        set { self[MainColorKey.self] = newValue }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
//            .environment(\.mainColor, .red)
    }
}
コード解説

まずは、structでKeyを作ります。

struct MainColorKey: EnvironmentKey {
    static let defaultValue: Color = .orange
}

このdefaultValueに入れる値がデフォルト値になります。

次に、extensionでEnvironmentValuesに値を追加します。

extension EnvironmentValues {
    var mainColor: Color {
        get { self[MainColorKey.self] }
        set { self[MainColorKey.self] = newValue }
    }
}

たったこれで、@Environmentの自作ができましたので、あとは通常通り上記の@Environmentと同じように使えます。

まとめ

まとめると、@Environmentというのは、その端末の状態や、Viewの状態を読み取れるカスタム属性です。たくさんの環境変数があるので何が読み取れるのかを一度全て確認しておいた方が良いかと思います。