iOS

SwiftUIのPreviewするデバイスをenumで選択できるようにしてみた

swiftui-preview-device-list-enum

SwiftUIのPreviewするデバイスをenumで選択できるようにしてみた。

SwiftUIでプレビューのデバイス指定はめんどう

SwiftUIでデバイスの種類を指定する時は、以下のように書くと思います。

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
      Group {
         ContentView()
            .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
            .previewDisplayName("iPhone SE")

         ContentView()
            .previewDevice(PreviewDevice(rawValue: "iPhone XS Max"))
            .previewDisplayName("iPhone XS Max")
      }
   }
}

しかし直接文字列で記述するのはタイポの可能性もありますし、そもそもなんて書けばいいんだっけ?と迷う→困る→ググる、といった不毛な時間を過ごすことになります。 そこで以下のようにenumで選択できるようにしました。

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice(device: .iPhone11Pro)
                .previewDisplayName(device: .iPhone11Pro)
            ContentView()
                .previewDevices(devices: [.iPhone11Pro, .iPadPro12_9inch])
        }
    }
}

これでタイポすることも、文字列として何を書けばいいのかと迷うこともありません。

実装の詳細

上記のコードを実現するには、カスタムのModifierを作成する必要があります。カスタムのModifierについてはこちらの記事が参考になりました。 以下、実装の詳細です。 ①Deviceというenumを作ります。

enum Device: String {
    // iPhone
    case iPhoneSE = "iPhone SE"
    case iPhone7 = "iPhone 7"
    case iPhone7Plus = "iPhone 7 Plus"
    case iPhone8 = "iPhone 8"
    case iPhone8Plus = "iPhone 8 Plus"
    case iPhoneX = "iPhone X"
    case iPhoneXs = "iPhone Xs"
    case iPhoneXsMax = "iPhone Xs Max"
    case iPhoneXR = "iPhone XR"
    case iPhone11 = "iPhone 11"
    case iPhone11Pro = "iPhone 11 Pro"
    case iPhone11ProMax = "iPhone 11 Pro Max"
    // iPad
    case iPadMini4 = "iPad mini 4"
    case iPadAir2 = "iPad Air 2"
    case iPadPro9_7inch = "iPad Pro (9.7-inch)"
    case iPadPro12_9inch = "iPad Pro (12.9-inch)"
    case iPad5th = "iPad (5th generation)"
    case iPadPro12_9inch2nd = "iPad Pro (12.9-inch) (2nd generation)"
    case iPadPro10_5inch = "iPad Pro (10.5-inch)"
    case iPad6th = "iPad (6th generation)"
    case iPadPro11inch = "iPad Pro (11-inch)"
    case iPadPro12_9inch3rd = "iPad Pro (12.9-inch) (3rd generation)"
    case iPadMini5th = "iPad mini (5th generation)"
    case iPadAir3rd = "iPad Air (3rd generation)"
    // AppleTV
    case AppleTV = "Apple TV"
    case AppleTV4K = "Apple TV 4K"
    case AppleTV4K_1080p = "Apple TV 4K (at 1080p)"
    // AppleWatch
    case AppleWatchS2_38mm = "Apple Watch Series 2 - 38mm"
    case AppleWatchS2_42mm = "Apple Watch Series 2 - 42mm"
    case AppleWatchS3_38mm = "Apple Watch Series 3 - 38mm"
    case AppleWatchS3_42mm = "Apple Watch Series 3 - 42mm"
    case AppleWatchS4_40mm = "Apple Watch Series 4 - 40mm"
    case AppleWatchS4_44mm = "Apple Watch Series 4 - 44mm"
    case AppleWatchS5_40mm = "Apple Watch Series 5 - 40mm"
    case AppleWatchS5_44mm = "Apple Watch Series 5 - 44mm"
}

②カスタムのViewModifierを作成する。

/// 選択したデバイスのPreviewを表示する。
struct DisplayPreviewDevice: ViewModifier {
    let device: Device

    func body(content: Content) -> some View {
        content
            .previewDevice(PreviewDevice(rawValue: device.rawValue))
    }
}

// Arrayで選択したデバイスを全て表示する。
struct DisplayPreviewDevices: ViewModifier {
    let devices: [Device]

    func body(content: Content) -> some View {
        ForEach(devices, id: \.self) { device in
            content
                .previewDevice(PreviewDevice(rawValue: device.rawValue))
        }
    }
}

// 選択したデバイスのデバイス名を表示する。
struct PreviewDisplayDeviceName: ViewModifier {
    let device: Device

    func body(content: Content) -> some View {
        content
            .previewDisplayName(device.rawValue)
    }
}

③作成したViewModifierViewのextensiionに追加する。

extension View {
    func previewDevice(device: Device) -> some View {
        ModifiedContent(content: self, modifier: DisplayPreviewDevice(device: device))
    } 

    func previewDevices(devices: [Device]) -> some View {
        ModifiedContent(content: self, modifier: DisplayPreviewDevices(devices: devices))
    }

    func previewDisplayName(device: Device) -> some View {
        ModifiedContent(content: self, modifier: PreviewDisplayDeviceName(device: device))
    }
}

これでPreviewも簡単に表示できるようになりました☺️

おまけ

デバイスのLight/Darkモードを指定して表示したいこともあると思います。 ついでにこちらも簡単に指定できるModifierを作成しました。

enum ColorMode {
    case light
    case dark
}

struct ColorScheme: ViewModifier {
    let color: ColorMode

    func body(content: Content) -> some View {
        switch color {
        case .light:
            return content
                .environment(\.colorScheme, .light)
        case .dark:
            return content
                .environment(\.colorScheme, .dark)
        }

    }
}

extension View {
    func colorScheme(color: ColorMode) -> some View {
        ModifiedContent(content: self, modifier: ColorScheme(color: color))
    }
}

使用例

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice(device: .iPhone11Pro)
                .previewDisplayName(device: .iPhone11Pro)
                // ホントはこんな感じで書く
                .environment(\.colorScheme, .dark)
            ContentView()
                .previewDevices(devices: [.iPhone11Pro, .iPadPro12_9inch])
                // enumで指定するだけで書ける!
                .colorScheme(.dark)
        }
    }
}

まとめ

enumで指定できるようにすることで、Previewを表示しすくなりました。 またPreviewを複数表示したい時もあると思うので、それを一つのメソッドで指定できるのは便利だと思いました。 ただpreviewDevicesを使用すると、iPhoneはライトモード、iPadはダークモードなど個別に設定を指定できないので、単純に複数デバイスでUIが崩れていないかを確認する場合にだけ使えそうです。 今後SwiftUIの開発も増えてくると思うので、SwiftUIもこうやってどんどん使いやすくしていければなーと思います。

注意点

enumのDeviceでしていしたデバイスの一覧は全てPreviewで正常に表示されるかを確認していないので、エラーになる場合もあるかもしれません。その場合はこっそり教えていただけるとうれしいです。こちら公式です。

参考資料

 【SwiftUI】カスタムModifierの作成 Apple Developer – previewDevice(_:)    

0

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA