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)
}
}
③作成したViewModifier
をView
の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(_:)